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