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