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