001/* 002 * nimbus-jose-jwt 003 * 004 * Copyright 2012-2016, Connect2id Ltd and contributors. 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.jwk; 019 020 021import java.net.URI; 022import java.security.KeyPair; 023import java.security.KeyStore; 024import java.security.PrivateKey; 025import java.security.PublicKey; 026import java.security.cert.X509Certificate; 027import java.text.ParseException; 028import java.util.*; 029 030import com.nimbusds.jose.Algorithm; 031import com.nimbusds.jose.JOSEException; 032import com.nimbusds.jose.util.Base64; 033import com.nimbusds.jose.util.Base64URL; 034import com.nimbusds.jose.util.ByteUtils; 035import com.nimbusds.jose.util.JSONObjectUtils; 036import net.jcip.annotations.Immutable; 037import net.minidev.json.JSONObject; 038 039 040/** 041 * {@link KeyType#OKP Octet key pair} JSON Web Key (JWK), used to represent 042 * Edwards-curve keys. This class is immutable. 043 * 044 * <p>Supported curves: 045 * 046 * <ul> 047 * <li>{@link Curve#Ed25519 Ed25519} 048 * <li>{@link Curve#Ed448 Ed448} 049 * <li>{@link Curve#X25519 X25519} 050 * <li>{@link Curve#X448 X448} 051 * </ul> 052 * 053 * <p>Example JSON object representation of a public OKP JWK: 054 * 055 * <pre> 056 * { 057 * "kty" : "OKP", 058 * "crv" : "Ed25519", 059 * "x" : "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo", 060 * "use" : "sig", 061 * "kid" : "1" 062 * } 063 * </pre> 064 * 065 * <p>Example JSON object representation of a private OKP JWK: 066 * 067 * <pre> 068 * { 069 * "kty" : "OKP", 070 * "crv" : "Ed25519", 071 * "x" : "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo", 072 * "d" : "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A" 073 * "use" : "sig", 074 * "kid" : "1" 075 * } 076 * </pre> 077 * 078 * <p>Use the builder to create a new OKP JWK: 079 * 080 * <pre> 081 * OctetKeyPair key = new OctetKeyPair.Builder(Curve.Ed25519, x) 082 * .keyUse(KeyUse.SIGNATURE) 083 * .keyID("1") 084 * .build(); 085 * </pre> 086 * 087 * @author Vladimir Dzhuvinov 088 * @version 2018-02-27 089 */ 090@Immutable 091public class OctetKeyPair extends JWK implements AsymmetricJWK, CurveBasedJWK { 092 093 094 private static final long serialVersionUID = 1L; 095 096 097 /** 098 * Supported Edwards curves. 099 */ 100 public static final Set<Curve> SUPPORTED_CURVES = Collections.unmodifiableSet( 101 new HashSet<>(Arrays.asList(Curve.Ed25519, Curve.Ed448, Curve.X25519, Curve.X448)) 102 ); 103 104 105 /** 106 * Builder for constructing Octet Key Pair JWKs. 107 * 108 * <p>Example usage: 109 * 110 * <pre> 111 * OctetKeyPair key = new OctetKeyPair.Builder(Curve.Ed25519, x) 112 * .d(d) 113 * .algorithm(JWSAlgorithm.EdDSA) 114 * .keyID("1") 115 * .build(); 116 * </pre> 117 */ 118 public static class Builder { 119 120 121 /** 122 * The curve name. 123 */ 124 private final Curve crv; 125 126 127 /** 128 * The public 'x' parameter. 129 */ 130 private final Base64URL x; 131 132 133 /** 134 * The private 'd' parameter, optional. 135 */ 136 private Base64URL d; 137 138 139 /** 140 * The key use, optional. 141 */ 142 private KeyUse use; 143 144 145 /** 146 * The key operations, optional. 147 */ 148 private Set<KeyOperation> ops; 149 150 151 /** 152 * The intended JOSE algorithm for the key, optional. 153 */ 154 private Algorithm alg; 155 156 157 /** 158 * The key ID, optional. 159 */ 160 private String kid; 161 162 163 /** 164 * X.509 certificate URL, optional. 165 */ 166 private URI x5u; 167 168 169 /** 170 * X.509 certificate SHA-1 thumbprint, optional. 171 */ 172 @Deprecated 173 private Base64URL x5t; 174 175 176 /** 177 * X.509 certificate SHA-256 thumbprint, optional. 178 */ 179 private Base64URL x5t256; 180 181 182 /** 183 * The X.509 certificate chain, optional. 184 */ 185 private List<Base64> x5c; 186 187 188 /** 189 * Reference to the underlying key store, {@code null} if none. 190 */ 191 private KeyStore ks; 192 193 194 /** 195 * Creates a new Octet Key Pair JWK builder. 196 * 197 * @param crv The cryptographic curve. Must not be 198 * {@code null}. 199 * @param x The public 'x' parameter. Must not be 200 * {@code null}. 201 */ 202 public Builder(final Curve crv, final Base64URL x) { 203 204 if (crv == null) { 205 throw new IllegalArgumentException("The curve must not be null"); 206 } 207 208 this.crv = crv; 209 210 if (x == null) { 211 throw new IllegalArgumentException("The 'x' coordinate must not be null"); 212 } 213 214 this.x = x; 215 } 216 217 218 /** 219 * Creates a new Octet Key Pair JWK builder. 220 * 221 * @param okpJWK The Octet Key Pair to start with. Must not be 222 * {@code null}. 223 */ 224 public Builder(final OctetKeyPair okpJWK) { 225 226 crv = okpJWK.crv; 227 x = okpJWK.x; 228 d = okpJWK.d; 229 use = okpJWK.getKeyUse(); 230 ops = okpJWK.getKeyOperations(); 231 alg = okpJWK.getAlgorithm(); 232 kid = okpJWK.getKeyID(); 233 x5u = okpJWK.getX509CertURL(); 234 x5t = okpJWK.getX509CertThumbprint(); 235 x5t256 = okpJWK.getX509CertSHA256Thumbprint(); 236 x5c = okpJWK.getX509CertChain(); 237 ks = okpJWK.getKeyStore(); 238 } 239 240 241 /** 242 * Sets the private 'd' parameter. 243 * 244 * @param d The private 'd' parameter, {@code null} if not 245 * specified (for a public key). 246 * 247 * @return This builder. 248 */ 249 public OctetKeyPair.Builder d(final Base64URL d) { 250 251 this.d = d; 252 return this; 253 } 254 255 256 /** 257 * Sets the use ({@code use}) of the JWK. 258 * 259 * @param use The key use, {@code null} if not specified or if 260 * the key is intended for signing as well as 261 * encryption. 262 * 263 * @return This builder. 264 */ 265 public OctetKeyPair.Builder keyUse(final KeyUse use) { 266 267 this.use = use; 268 return this; 269 } 270 271 272 /** 273 * Sets the operations ({@code key_ops}) of the JWK. 274 * 275 * @param ops The key operations, {@code null} if not 276 * specified. 277 * 278 * @return This builder. 279 */ 280 public OctetKeyPair.Builder keyOperations(final Set<KeyOperation> ops) { 281 282 this.ops = ops; 283 return this; 284 } 285 286 287 /** 288 * Sets the intended JOSE algorithm ({@code alg}) for the JWK. 289 * 290 * @param alg The intended JOSE algorithm, {@code null} if not 291 * specified. 292 * 293 * @return This builder. 294 */ 295 public OctetKeyPair.Builder algorithm(final Algorithm alg) { 296 297 this.alg = alg; 298 return this; 299 } 300 301 /** 302 * Sets the ID ({@code kid}) of the JWK. The key ID can be used 303 * to match a specific key. This can be used, for instance, to 304 * choose a key within a {@link JWKSet} during key rollover. 305 * The key ID may also correspond to a JWS/JWE {@code kid} 306 * header parameter value. 307 * 308 * @param kid The key ID, {@code null} if not specified. 309 * 310 * @return This builder. 311 */ 312 public OctetKeyPair.Builder keyID(final String kid) { 313 314 this.kid = kid; 315 return this; 316 } 317 318 319 /** 320 * Sets the ID ({@code kid}) of the JWK to its SHA-256 JWK 321 * thumbprint (RFC 7638). The key ID can be used to match a 322 * specific key. This can be used, for instance, to choose a 323 * key within a {@link JWKSet} during key rollover. The key ID 324 * may also correspond to a JWS/JWE {@code kid} header 325 * parameter value. 326 * 327 * @return This builder. 328 * 329 * @throws JOSEException If the SHA-256 hash algorithm is not 330 * supported. 331 */ 332 public OctetKeyPair.Builder keyIDFromThumbprint() 333 throws JOSEException { 334 335 return keyIDFromThumbprint("SHA-256"); 336 } 337 338 339 /** 340 * Sets the ID ({@code kid}) of the JWK to its JWK thumbprint 341 * (RFC 7638). The key ID can be used to match a specific key. 342 * This can be used, for instance, to choose a key within a 343 * {@link JWKSet} during key rollover. The key ID may also 344 * correspond to a JWS/JWE {@code kid} header parameter value. 345 * 346 * @param hashAlg The hash algorithm for the JWK thumbprint 347 * computation. Must not be {@code null}. 348 * 349 * @return This builder. 350 * 351 * @throws JOSEException If the hash algorithm is not 352 * supported. 353 */ 354 public OctetKeyPair.Builder keyIDFromThumbprint(final String hashAlg) 355 throws JOSEException { 356 357 // Put mandatory params in sorted order 358 LinkedHashMap<String,String> requiredParams = new LinkedHashMap<>(); 359 requiredParams.put("crv", crv.toString()); 360 requiredParams.put("kty", KeyType.OKP.getValue()); 361 requiredParams.put("x", x.toString()); 362 this.kid = ThumbprintUtils.compute(hashAlg, requiredParams).toString(); 363 return this; 364 } 365 366 367 /** 368 * Sets the X.509 certificate URL ({@code x5u}) of the JWK. 369 * 370 * @param x5u The X.509 certificate URL, {@code null} if not 371 * specified. 372 * 373 * @return This builder. 374 */ 375 public OctetKeyPair.Builder x509CertURL(final URI x5u) { 376 377 this.x5u = x5u; 378 return this; 379 } 380 381 382 /** 383 * Sets the X.509 certificate SHA-1 thumbprint ({@code x5t}) of 384 * the JWK. 385 * 386 * @param x5t The X.509 certificate SHA-1 thumbprint, 387 * {@code null} if not specified. 388 * 389 * @return This builder. 390 */ 391 @Deprecated 392 public OctetKeyPair.Builder x509CertThumbprint(final Base64URL x5t) { 393 394 this.x5t = x5t; 395 return this; 396 } 397 398 399 /** 400 * Sets the X.509 certificate SHA-256 thumbprint 401 * ({@code x5t#S256}) of the JWK. 402 * 403 * @param x5t256 The X.509 certificate SHA-256 thumbprint, 404 * {@code null} if not specified. 405 * 406 * @return This builder. 407 */ 408 public OctetKeyPair.Builder x509CertSHA256Thumbprint(final Base64URL x5t256) { 409 410 this.x5t256 = x5t256; 411 return this; 412 } 413 414 415 /** 416 * Sets the X.509 certificate chain ({@code x5c}) of the JWK. 417 * 418 * @param x5c The X.509 certificate chain as a unmodifiable 419 * list, {@code null} if not specified. 420 * 421 * @return This builder. 422 */ 423 public OctetKeyPair.Builder x509CertChain(final List<Base64> x5c) { 424 425 this.x5c = x5c; 426 return this; 427 } 428 429 430 /** 431 * Sets the underlying key store. 432 * 433 * @param keyStore Reference to the underlying key store, 434 * {@code null} if none. 435 * 436 * @return This builder. 437 */ 438 public OctetKeyPair.Builder keyStore(final KeyStore keyStore) { 439 440 this.ks = keyStore; 441 return this; 442 } 443 444 445 /** 446 * Builds a new Octet Key Pair JWK. 447 * 448 * @return The Octet Key Pair JWK. 449 * 450 * @throws IllegalStateException If the JWK parameters were 451 * inconsistently specified. 452 */ 453 public OctetKeyPair build() { 454 455 try { 456 if (d == null) { 457 // Public key 458 return new OctetKeyPair(crv, x, use, ops, alg, kid, x5u, x5t, x5t256, x5c, ks); 459 } 460 461 // Public / private key pair with 'd' 462 return new OctetKeyPair(crv, x, d, use, ops, alg, kid, x5u, x5t, x5t256, x5c, ks); 463 464 } catch (IllegalArgumentException e) { 465 throw new IllegalStateException(e.getMessage(), e); 466 } 467 } 468 } 469 470 471 /** 472 * The curve name. 473 */ 474 private final Curve crv; 475 476 477 /** 478 * The public 'x' parameter. 479 */ 480 private final Base64URL x; 481 482 483 /** 484 * The private 'd' parameter. 485 */ 486 private final Base64URL d; 487 488 489 /** 490 * Creates a new public Octet Key Pair JSON Web Key (JWK) with the 491 * specified parameters. 492 * 493 * @param crv The cryptographic curve. Must not be {@code null}. 494 * @param x The public 'x' parameter. Must not be {@code null}. 495 * @param use The key use, {@code null} if not specified or if the 496 * key is intended for signing as well as encryption. 497 * @param ops The key operations, {@code null} if not specified. 498 * @param alg The intended JOSE algorithm for the key, {@code null} 499 * if not specified. 500 * @param kid The key ID, {@code null} if not specified. 501 * @param x5u The X.509 certificate URL, {@code null} if not 502 * specified. 503 * @param x5t The X.509 certificate SHA-1 thumbprint, {@code null} 504 * if not specified. 505 * @param x5t256 The X.509 certificate SHA-256 thumbprint, {@code null} 506 * if not specified. 507 * @param x5c The X.509 certificate chain, {@code null} if not 508 * specified. 509 * @param ks Reference to the underlying key store, {@code null} if 510 * not specified. 511 */ 512 public OctetKeyPair(final Curve crv, final Base64URL x, 513 final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, 514 final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, 515 final KeyStore ks) { 516 517 super(KeyType.OKP, use, ops, alg, kid, x5u, x5t, x5t256, x5c, ks); 518 519 if (crv == null) { 520 throw new IllegalArgumentException("The curve must not be null"); 521 } 522 523 if (! SUPPORTED_CURVES.contains(crv)) { 524 throw new IllegalArgumentException("Unknown / unsupported curve: " + crv); 525 } 526 527 this.crv = crv; 528 529 if (x == null) { 530 throw new IllegalArgumentException("The 'x' parameter must not be null"); 531 } 532 533 this.x = x; 534 535 d = null; 536 } 537 538 539 /** 540 * Creates a new public / private Octet Key Pair JSON Web Key (JWK) 541 * with the specified parameters. 542 * 543 * @param crv The cryptographic curve. Must not be {@code null}. 544 * @param x The public 'x' parameter. Must not be {@code null}. 545 * @param d The private 'd' parameter. Must not be {@code null}. 546 * @param use The key use, {@code null} if not specified or if the 547 * key is intended for signing as well as encryption. 548 * @param ops The key operations, {@code null} if not specified. 549 * @param alg The intended JOSE algorithm for the key, {@code null} 550 * if not specified. 551 * @param kid The key ID, {@code null} if not specified. 552 * @param x5u The X.509 certificate URL, {@code null} if not 553 * specified. 554 * @param x5t The X.509 certificate SHA-1 thumbprint, {@code null} 555 * if not specified. 556 * @param x5t256 The X.509 certificate SHA-256 thumbprint, {@code null} 557 * if not specified. 558 * @param x5c The X.509 certificate chain, {@code null} if not 559 * specified. 560 * @param ks Reference to the underlying key store, {@code null} if 561 * not specified. 562 */ 563 public OctetKeyPair(final Curve crv, final Base64URL x, final Base64URL d, 564 final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, 565 final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, 566 final KeyStore ks) { 567 568 super(KeyType.OKP, use, ops, alg, kid, x5u, x5t, x5t256, x5c, ks); 569 570 if (crv == null) { 571 throw new IllegalArgumentException("The curve must not be null"); 572 } 573 574 if (! SUPPORTED_CURVES.contains(crv)) { 575 throw new IllegalArgumentException("Unknown / unsupported curve: " + crv); 576 } 577 578 this.crv = crv; 579 580 if (x == null) { 581 throw new IllegalArgumentException("The 'x' parameter must not be null"); 582 } 583 584 this.x = x; 585 586 if (d == null) { 587 throw new IllegalArgumentException("The 'd' parameter must not be null"); 588 } 589 590 this.d = d; 591 } 592 593 594 @Override 595 public Curve getCurve() { 596 597 return crv; 598 } 599 600 601 /** 602 * Gets the public 'x' parameter. 603 * 604 * @return The public 'x' parameter. 605 */ 606 public Base64URL getX() { 607 608 return x; 609 } 610 611 612 /** 613 * Gets the private 'd' parameter. 614 * 615 * @return The private 'd' coordinate, {@code null} if not specified 616 * (for a public key). 617 */ 618 public Base64URL getD() { 619 620 return d; 621 } 622 623 624 @Override 625 public PublicKey toPublicKey() 626 throws JOSEException { 627 628 throw new JOSEException("Export to java.security.PublicKey not supported"); 629 } 630 631 632 @Override 633 public PrivateKey toPrivateKey() 634 throws JOSEException { 635 636 throw new JOSEException("Export to java.security.PrivateKey not supported"); 637 } 638 639 640 @Override 641 public KeyPair toKeyPair() 642 throws JOSEException { 643 644 throw new JOSEException("Export to java.security.KeyPair not supported"); 645 } 646 647 648 @Override 649 public boolean matches(final X509Certificate cert) { 650 // X.509 certs don't support OKP yet 651 return false; 652 } 653 654 655 @Override 656 public LinkedHashMap<String,?> getRequiredParams() { 657 658 // Put mandatory params in sorted order 659 LinkedHashMap<String,String> requiredParams = new LinkedHashMap<>(); 660 requiredParams.put("crv", crv.toString()); 661 requiredParams.put("kty", getKeyType().getValue()); 662 requiredParams.put("x", x.toString()); 663 return requiredParams; 664 } 665 666 667 @Override 668 public boolean isPrivate() { 669 670 return d != null; 671 } 672 673 674 /** 675 * Returns a copy of this Octet Key Pair JWK with any private values 676 * removed. 677 * 678 * @return The copied public Octet Key Pair JWK. 679 */ 680 @Override 681 public OctetKeyPair toPublicJWK() { 682 683 return new OctetKeyPair( 684 getCurve(), getX(), 685 getKeyUse(), getKeyOperations(), getAlgorithm(), getKeyID(), 686 getX509CertURL(), getX509CertThumbprint(), getX509CertSHA256Thumbprint(), getX509CertChain(), 687 getKeyStore()); 688 } 689 690 691 @Override 692 public JSONObject toJSONObject() { 693 694 JSONObject o = super.toJSONObject(); 695 696 // Append OKP specific attributes 697 o.put("crv", crv.toString()); 698 o.put("x", x.toString()); 699 700 if (d != null) { 701 o.put("d", d.toString()); 702 } 703 704 return o; 705 } 706 707 708 @Override 709 public int size() { 710 711 return ByteUtils.bitLength(x.decode()); 712 } 713 714 715 /** 716 * Parses a public / private Octet Key Pair JWK from the specified JSON 717 * object string representation. 718 * 719 * @param s The JSON object string to parse. Must not be {@code null}. 720 * 721 * @return The public / private Octet Key Pair JWK. 722 * 723 * @throws ParseException If the string couldn't be parsed to an Octet 724 * Key Pair JWK. 725 */ 726 public static OctetKeyPair parse(final String s) 727 throws ParseException { 728 729 return parse(JSONObjectUtils.parse(s)); 730 } 731 732 733 /** 734 * Parses a public / private Octet Key Pair JWK from the specified JSON 735 * object representation. 736 * 737 * @param jsonObject The JSON object to parse. Must not be 738 * {@code null}. 739 * 740 * @return The public / private Octet Key Pair JWK. 741 * 742 * @throws ParseException If the JSON object couldn't be parsed to an 743 * Octet Key Pair JWK. 744 */ 745 public static OctetKeyPair parse(final JSONObject jsonObject) 746 throws ParseException { 747 748 // Parse the mandatory parameters first 749 Curve crv = Curve.parse(JSONObjectUtils.getString(jsonObject, "crv")); 750 Base64URL x = new Base64URL(JSONObjectUtils.getString(jsonObject, "x")); 751 752 // Check key type 753 KeyType kty = JWKMetadata.parseKeyType(jsonObject); 754 755 if (kty != KeyType.OKP) { 756 throw new ParseException("The key type \"kty\" must be OKP", 0); 757 } 758 759 // Get optional private key 760 Base64URL d = null; 761 if (jsonObject.get("d") != null) { 762 d = new Base64URL(JSONObjectUtils.getString(jsonObject, "d")); 763 } 764 765 766 try { 767 if (d == null) { 768 // Public key 769 return new OctetKeyPair(crv, x, 770 JWKMetadata.parseKeyUse(jsonObject), 771 JWKMetadata.parseKeyOperations(jsonObject), 772 JWKMetadata.parseAlgorithm(jsonObject), 773 JWKMetadata.parseKeyID(jsonObject), 774 JWKMetadata.parseX509CertURL(jsonObject), 775 JWKMetadata.parseX509CertThumbprint(jsonObject), 776 JWKMetadata.parseX509CertSHA256Thumbprint(jsonObject), 777 JWKMetadata.parseX509CertChain(jsonObject), 778 null); 779 780 } else { 781 // Key pair 782 return new OctetKeyPair(crv, x, d, 783 JWKMetadata.parseKeyUse(jsonObject), 784 JWKMetadata.parseKeyOperations(jsonObject), 785 JWKMetadata.parseAlgorithm(jsonObject), 786 JWKMetadata.parseKeyID(jsonObject), 787 JWKMetadata.parseX509CertURL(jsonObject), 788 JWKMetadata.parseX509CertThumbprint(jsonObject), 789 JWKMetadata.parseX509CertSHA256Thumbprint(jsonObject), 790 JWKMetadata.parseX509CertChain(jsonObject), 791 null); 792 } 793 794 } catch (IllegalArgumentException ex) { 795 796 // Conflicting 'use' and 'key_ops' 797 throw new ParseException(ex.getMessage(), 0); 798 } 799 } 800}