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