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