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.io.Serializable; 022import java.net.URI; 023import java.security.KeyStore; 024import java.security.KeyStoreException; 025import java.security.cert.X509Certificate; 026import java.security.interfaces.ECPublicKey; 027import java.security.interfaces.RSAPublicKey; 028import java.text.ParseException; 029import java.util.*; 030 031import com.nimbusds.jose.Algorithm; 032import com.nimbusds.jose.JOSEException; 033import com.nimbusds.jose.util.Base64; 034import com.nimbusds.jose.util.Base64URL; 035import com.nimbusds.jose.util.JSONObjectUtils; 036import com.nimbusds.jose.util.X509CertChainUtils; 037import net.minidev.json.JSONAware; 038import net.minidev.json.JSONObject; 039 040 041/** 042 * The base abstract class for JSON Web Keys (JWKs). It serialises to a JSON 043 * object. 044 * 045 * <p>The following JSON object members are common to all JWK types: 046 * 047 * <ul> 048 * <li>{@link #getKeyType kty} (required) 049 * <li>{@link #getKeyUse use} (optional) 050 * <li>{@link #getKeyOperations key_ops} (optional) 051 * <li>{@link #getKeyID kid} (optional) 052 * <li>{@link #getX509CertURL() x5u} (optional) 053 * <li>{@link #getX509CertThumbprint() x5t} (optional) 054 * <li>{@link #getX509CertSHA256Thumbprint() x5t#S256} (optional) 055 * <li>{@link #getX509CertChain() x5c} (optional) 056 * <li>{@link #getKeyStore()} 057 * </ul> 058 * 059 * <p>Example JWK (of the Elliptic Curve type): 060 * 061 * <pre> 062 * { 063 * "kty" : "EC", 064 * "crv" : "P-256", 065 * "x" : "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", 066 * "y" : "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", 067 * "use" : "enc", 068 * "kid" : "1" 069 * } 070 * </pre> 071 * 072 * @author Vladimir Dzhuvinov 073 * @author Justin Richer 074 * @version 2018-02-27 075 */ 076public abstract class JWK implements JSONAware, Serializable { 077 078 079 private static final long serialVersionUID = 1L; 080 081 082 /** 083 * The MIME type of JWK objects: 084 * {@code application/jwk+json; charset=UTF-8} 085 */ 086 public static final String MIME_TYPE = "application/jwk+json; charset=UTF-8"; 087 088 089 /** 090 * The key type, required. 091 */ 092 private final KeyType kty; 093 094 095 /** 096 * The key use, optional. 097 */ 098 private final KeyUse use; 099 100 101 /** 102 * The key operations, optional. 103 */ 104 private final Set<KeyOperation> ops; 105 106 107 /** 108 * The intended JOSE algorithm for the key, optional. 109 */ 110 private final Algorithm alg; 111 112 113 /** 114 * The key ID, optional. 115 */ 116 private final String kid; 117 118 119 /** 120 * X.509 certificate URL, optional. 121 */ 122 private final URI x5u; 123 124 125 /** 126 * X.509 certificate SHA-1 thumbprint, optional. 127 */ 128 @Deprecated 129 private final Base64URL x5t; 130 131 132 /** 133 * X.509 certificate SHA-256 thumbprint, optional. 134 */ 135 private Base64URL x5t256; 136 137 138 /** 139 * The X.509 certificate chain, optional. 140 */ 141 private final List<Base64> x5c; 142 143 144 /** 145 * The parsed X.509 certificate chain, optional. 146 */ 147 private final List<X509Certificate> parsedX5c; 148 149 150 /** 151 * Reference to the underlying key store, {@code null} if none. 152 */ 153 private final KeyStore keyStore; 154 155 156 /** 157 * Creates a new JSON Web Key (JWK). 158 * 159 * @param kty The key type. Must not be {@code null}. 160 * @param use The key use, {@code null} if not specified or if the 161 * key is intended for signing as well as encryption. 162 * @param ops The key operations, {@code null} if not specified. 163 * @param alg The intended JOSE algorithm for the key, {@code null} 164 * if not specified. 165 * @param kid The key ID, {@code null} if not specified. 166 * @param x5u The X.509 certificate URL, {@code null} if not 167 * specified. 168 * @param x5t The X.509 certificate thumbprint, {@code null} if not 169 * specified. 170 * @param x5t256 The X.509 certificate SHA-256 thumbprint, {@code null} 171 * if not specified. 172 * @param x5c The X.509 certificate chain, {@code null} if not 173 * specified. 174 * @param ks Reference to the underlying key store, {@code null} if 175 * none. 176 */ 177 protected JWK(final KeyType kty, 178 final KeyUse use, 179 final Set<KeyOperation> ops, 180 final Algorithm alg, 181 final String kid, 182 final URI x5u, 183 final Base64URL x5t, 184 final Base64URL x5t256, 185 final List<Base64> x5c, 186 final KeyStore ks) { 187 188 if (kty == null) { 189 throw new IllegalArgumentException("The key type \"kty\" parameter must not be null"); 190 } 191 192 this.kty = kty; 193 194 if (! KeyUseAndOpsConsistency.areConsistent(use, ops)) { 195 throw new IllegalArgumentException("The key use \"use\" and key options \"key_opts\" parameters are not consistent, " + 196 "see RFC 7517, section 4.3"); 197 } 198 199 this.use = use; 200 this.ops = ops; 201 202 this.alg = alg; 203 this.kid = kid; 204 205 this.x5u = x5u; 206 this.x5t = x5t; 207 this.x5t256 = x5t256; 208 209 if (x5c != null && x5c.isEmpty()) { 210 throw new IllegalArgumentException("The X.509 certificate chain \"x5c\" must not be empty"); 211 } 212 this.x5c = x5c; 213 214 try { 215 parsedX5c = X509CertChainUtils.parse(x5c); 216 } catch (ParseException e) { 217 throw new IllegalArgumentException("Invalid X.509 certificate chain \"x5c\": " + e.getMessage(), e); 218 } 219 220 this.keyStore = ks; 221 } 222 223 224 /** 225 * Gets the type ({@code kty}) of this JWK. 226 * 227 * @return The key type. 228 */ 229 public KeyType getKeyType() { 230 231 return kty; 232 } 233 234 235 /** 236 * Gets the use ({@code use}) of this JWK. 237 * 238 * @return The key use, {@code null} if not specified or if the key is 239 * intended for signing as well as encryption. 240 */ 241 public KeyUse getKeyUse() { 242 243 return use; 244 } 245 246 247 /** 248 * Gets the operations ({@code key_ops}) for this JWK. 249 * 250 * @return The key operations, {@code null} if not specified. 251 */ 252 public Set<KeyOperation> getKeyOperations() { 253 254 return ops; 255 } 256 257 258 /** 259 * Gets the intended JOSE algorithm ({@code alg}) for this JWK. 260 * 261 * @return The intended JOSE algorithm, {@code null} if not specified. 262 */ 263 public Algorithm getAlgorithm() { 264 265 return alg; 266 } 267 268 269 /** 270 * Gets the ID ({@code kid}) of this JWK. The key ID can be used to 271 * match a specific key. This can be used, for instance, to choose a 272 * key within a {@link JWKSet} during key rollover. The key ID may also 273 * correspond to a JWS/JWE {@code kid} header parameter value. 274 * 275 * @return The key ID, {@code null} if not specified. 276 */ 277 public String getKeyID() { 278 279 return kid; 280 } 281 282 283 /** 284 * Gets the X.509 certificate URL ({@code x5u}) of this JWK. 285 * 286 * @return The X.509 certificate URL, {@code null} if not specified. 287 */ 288 public URI getX509CertURL() { 289 290 return x5u; 291 } 292 293 294 /** 295 * Gets the X.509 certificate SHA-1 thumbprint ({@code x5t}) of this 296 * JWK. 297 * 298 * @return The X.509 certificate SHA-1 thumbprint, {@code null} if not 299 * specified. 300 */ 301 @Deprecated 302 public Base64URL getX509CertThumbprint() { 303 304 return x5t; 305 } 306 307 308 /** 309 * Gets the X.509 certificate SHA-256 thumbprint ({@code x5t#S256}) of 310 * this JWK. 311 * 312 * @return The X.509 certificate SHA-256 thumbprint, {@code null} if 313 * not specified. 314 */ 315 public Base64URL getX509CertSHA256Thumbprint() { 316 317 return x5t256; 318 } 319 320 321 /** 322 * Gets the X.509 certificate chain ({@code x5c}) of this JWK. 323 * 324 * @return The X.509 certificate chain as a unmodifiable list, 325 * {@code null} if not specified. 326 */ 327 public List<Base64> getX509CertChain() { 328 329 if (x5c == null) { 330 return null; 331 } 332 333 return Collections.unmodifiableList(x5c); 334 } 335 336 337 /** 338 * Gets the parsed X.509 certificate chain ({@code x5c}) of this JWK. 339 * 340 * @return The X.509 certificate chain as a unmodifiable list, 341 * {@code null} if not specified. 342 */ 343 public List<X509Certificate> getParsedX509CertChain() { 344 345 if (parsedX5c == null) { 346 return null; 347 } 348 349 return Collections.unmodifiableList(parsedX5c); 350 } 351 352 353 /** 354 * Returns a reference to the underlying key store. 355 * 356 * @return The underlying key store, {@code null} if none. 357 */ 358 public KeyStore getKeyStore() { 359 360 return keyStore; 361 } 362 363 364 /** 365 * Returns the required JWK parameters. Intended as input for JWK 366 * thumbprint computation. See RFC 7638 for more information. 367 * 368 * @return The required JWK parameters, sorted alphanumerically by key 369 * name and ready for JSON serialisation. 370 */ 371 public abstract LinkedHashMap<String,?> getRequiredParams(); 372 373 374 /** 375 * Computes the SHA-256 thumbprint of this JWK. See RFC 7638 for more 376 * information. 377 * 378 * @return The SHA-256 thumbprint. 379 * 380 * @throws JOSEException If the SHA-256 hash algorithm is not 381 * supported. 382 */ 383 public Base64URL computeThumbprint() 384 throws JOSEException { 385 386 return computeThumbprint("SHA-256"); 387 } 388 389 390 /** 391 * Computes the thumbprint of this JWK using the specified hash 392 * algorithm. See RFC 7638 for more information. 393 * 394 * @param hashAlg The hash algorithm. Must not be {@code null}. 395 * 396 * @return The SHA-256 thumbprint. 397 * 398 * @throws JOSEException If the hash algorithm is not supported. 399 */ 400 public Base64URL computeThumbprint(final String hashAlg) 401 throws JOSEException { 402 403 return ThumbprintUtils.compute(hashAlg, this); 404 } 405 406 407 /** 408 * Returns {@code true} if this JWK contains private or sensitive 409 * (non-public) parameters. 410 * 411 * @return {@code true} if this JWK contains private parameters, else 412 * {@code false}. 413 */ 414 public abstract boolean isPrivate(); 415 416 417 /** 418 * Creates a copy of this JWK with all private or sensitive parameters 419 * removed. 420 * 421 * @return The newly created public JWK, or {@code null} if none can be 422 * created. 423 */ 424 public abstract JWK toPublicJWK(); 425 426 427 /** 428 * Returns the size of this JWK. 429 * 430 * @return The JWK size, in bits. 431 */ 432 public abstract int size(); 433 434 435 /** 436 * Returns a JSON object representation of this JWK. This method is 437 * intended to be called from extending classes. 438 * 439 * <p>Example: 440 * 441 * <pre> 442 * { 443 * "kty" : "RSA", 444 * "use" : "sig", 445 * "kid" : "fd28e025-8d24-48bc-a51a-e2ffc8bc274b" 446 * } 447 * </pre> 448 * 449 * @return The JSON object representation. 450 */ 451 public JSONObject toJSONObject() { 452 453 JSONObject o = new JSONObject(); 454 455 o.put("kty", kty.getValue()); 456 457 if (use != null) { 458 o.put("use", use.identifier()); 459 } 460 461 if (ops != null) { 462 463 List<String> sl = new ArrayList<>(ops.size()); 464 465 for (KeyOperation op: ops) { 466 sl.add(op.identifier()); 467 } 468 469 o.put("key_ops", sl); 470 } 471 472 if (alg != null) { 473 o.put("alg", alg.getName()); 474 } 475 476 if (kid != null) { 477 o.put("kid", kid); 478 } 479 480 if (x5u != null) { 481 o.put("x5u", x5u.toString()); 482 } 483 484 if (x5t != null) { 485 o.put("x5t", x5t.toString()); 486 } 487 488 if (x5t256 != null) { 489 o.put("x5t#S256", x5t256.toString()); 490 } 491 492 if (x5c != null) { 493 o.put("x5c", x5c); 494 } 495 496 return o; 497 } 498 499 500 /** 501 * Returns the JSON object string representation of this JWK. 502 * 503 * @return The JSON object string representation. 504 */ 505 @Override 506 public String toJSONString() { 507 508 return toJSONObject().toString(); 509 } 510 511 512 /** 513 * @see #toJSONString 514 */ 515 @Override 516 public String toString() { 517 518 return toJSONObject().toString(); 519 } 520 521 522 /** 523 * Parses a JWK from the specified JSON object string representation. 524 * The JWK must be an {@link ECKey}, an {@link RSAKey}, or a 525 * {@link OctetSequenceKey}. 526 * 527 * @param s The JSON object string to parse. Must not be {@code null}. 528 * 529 * @return The JWK. 530 * 531 * @throws ParseException If the string couldn't be parsed to a 532 * supported JWK. 533 */ 534 public static JWK parse(final String s) 535 throws ParseException { 536 537 return parse(JSONObjectUtils.parse(s)); 538 } 539 540 541 /** 542 * Parses a JWK from the specified JSON object representation. The JWK 543 * must be an {@link ECKey}, an {@link RSAKey}, or a 544 * {@link OctetSequenceKey}. 545 * 546 * @param jsonObject The JSON object to parse. Must not be 547 * {@code null}. 548 * 549 * @return The JWK. 550 * 551 * @throws ParseException If the JSON object couldn't be parsed to a 552 * supported JWK. 553 */ 554 public static JWK parse(final JSONObject jsonObject) 555 throws ParseException { 556 557 KeyType kty = KeyType.parse(JSONObjectUtils.getString(jsonObject, "kty")); 558 559 if (kty == KeyType.EC) { 560 561 return ECKey.parse(jsonObject); 562 563 } else if (kty == KeyType.RSA) { 564 565 return RSAKey.parse(jsonObject); 566 567 } else if (kty == KeyType.OCT) { 568 569 return OctetSequenceKey.parse(jsonObject); 570 571 } else if (kty == KeyType.OKP) { 572 573 return OctetKeyPair.parse(jsonObject); 574 575 } else { 576 577 throw new ParseException("Unsupported key type \"kty\" parameter: " + kty, 0); 578 } 579 } 580 581 582 /** 583 * Parses a public {@link RSAKey RSA} or {@link ECKey EC JWK} from the 584 * specified X.509 certificate. Requires BouncyCastle. 585 * 586 * <p><strong>Important:</strong> The X.509 certificate is not 587 * validated! 588 * 589 * <p>Sets the following JWK parameters: 590 * 591 * <ul> 592 * <li>For an EC key the curve is obtained from the subject public 593 * key info algorithm parameters. 594 * <li>The JWK use inferred by {@link KeyUse#from}. 595 * <li>The JWK ID from the X.509 serial number (in base 10). 596 * <li>The JWK X.509 certificate chain (this certificate only). 597 * <li>The JWK X.509 certificate SHA-256 thumbprint. 598 * </ul> 599 * 600 * @param cert The X.509 certificate. Must not be {@code null}. 601 * 602 * @return The public RSA or EC JWK. 603 * 604 * @throws JOSEException If parsing failed. 605 */ 606 public static JWK parse(final X509Certificate cert) 607 throws JOSEException { 608 609 if (cert.getPublicKey() instanceof RSAPublicKey) { 610 return RSAKey.parse(cert); 611 } else if (cert.getPublicKey() instanceof ECPublicKey) { 612 return ECKey.parse(cert); 613 } else { 614 throw new JOSEException("Unsupported public key algorithm: " + cert.getPublicKey().getAlgorithm()); 615 } 616 } 617 618 619 /** 620 * Loads a JWK from the specified JCE key store. The JWK can be a 621 * public / private {@link RSAKey RSA key}, a public / private 622 * {@link ECKey EC key}, or a {@link OctetSequenceKey secret key}. 623 * Requires BouncyCastle. 624 * 625 * <p><strong>Important:</strong> The X.509 certificate is not 626 * validated! 627 * 628 * @param keyStore The key store. Must not be {@code null}. 629 * @param alias The alias. Must not be {@code null}. 630 * @param pin The pin to unlock the private key if any, empty or 631 * {@code null} if not required. 632 * 633 * @return The public / private RSA or EC JWK, or secret JWK, or 634 * {@code null} if no key with the specified alias was found. 635 * 636 * @throws KeyStoreException On a key store exception. 637 * @throws JOSEException If RSA or EC key loading failed. 638 */ 639 public static JWK load(final KeyStore keyStore, final String alias, final char[] pin) 640 throws KeyStoreException, JOSEException { 641 642 java.security.cert.Certificate cert = keyStore.getCertificate(alias); 643 644 if (cert == null) { 645 // Try secret key 646 return OctetSequenceKey.load(keyStore, alias, pin); 647 } 648 649 if (cert.getPublicKey() instanceof RSAPublicKey) { 650 return RSAKey.load(keyStore, alias, pin); 651 } else if (cert.getPublicKey() instanceof ECPublicKey) { 652 return ECKey.load(keyStore, alias, pin); 653 } else { 654 throw new JOSEException("Unsupported public key algorithm: " + cert.getPublicKey().getAlgorithm()); 655 } 656 } 657}