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