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