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