001package com.nimbusds.jose.jwk; 002 003 004import java.io.Serializable; 005import java.net.URI; 006import java.text.ParseException; 007import java.util.*; 008 009import com.nimbusds.jose.Algorithm; 010import com.nimbusds.jose.JOSEException; 011import com.nimbusds.jose.util.Base64; 012import com.nimbusds.jose.util.Base64URL; 013import com.nimbusds.jose.util.JSONObjectUtils; 014import net.minidev.json.JSONAware; 015import net.minidev.json.JSONObject; 016 017 018/** 019 * The base abstract class for JSON Web Keys (JWKs). It serialises to a JSON 020 * object. 021 * 022 * <p>The following JSON object members are common to all JWK types: 023 * 024 * <ul> 025 * <li>{@link #getKeyType kty} (required) 026 * <li>{@link #getKeyUse use} (optional) 027 * <li>{@link #getKeyOperations key_ops} (optional) 028 * <li>{@link #getKeyID kid} (optional) 029 * </ul> 030 * 031 * <p>Example JWK (of the Elliptic Curve type): 032 * 033 * <pre> 034 * { 035 * "kty" : "EC", 036 * "crv" : "P-256", 037 * "x" : "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", 038 * "y" : "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", 039 * "use" : "enc", 040 * "kid" : "1" 041 * } 042 * </pre> 043 * 044 * @author Vladimir Dzhuvinov 045 * @author Justin Richer 046 * @version 2016-07-03 047 */ 048public abstract class JWK implements JSONAware, Serializable { 049 050 051 private static final long serialVersionUID = 1L; 052 053 054 /** 055 * The MIME type of JWK objects: 056 * {@code application/jwk+json; charset=UTF-8} 057 */ 058 public static final String MIME_TYPE = "application/jwk+json; charset=UTF-8"; 059 060 061 /** 062 * The key type, required. 063 */ 064 private final KeyType kty; 065 066 067 /** 068 * The key use, optional. 069 */ 070 private final KeyUse use; 071 072 073 /** 074 * The key operations, optional. 075 */ 076 private final Set<KeyOperation> ops; 077 078 079 /** 080 * The intended JOSE algorithm for the key, optional. 081 */ 082 private final Algorithm alg; 083 084 085 /** 086 * The key ID, optional. 087 */ 088 private final String kid; 089 090 091 /** 092 * X.509 certificate URL, optional. 093 */ 094 private final URI x5u; 095 096 097 /** 098 * X.509 certificate thumbprint, optional. 099 */ 100 private final Base64URL x5t; 101 102 103 /** 104 * The X.509 certificate chain, optional. 105 */ 106 private final List<Base64> x5c; 107 108 109 /** 110 * Creates a new JSON Web Key (JWK). 111 * 112 * @param kty The key type. Must not be {@code null}. 113 * @param use The key use, {@code null} if not specified or if the key 114 * is intended for signing as well as encryption. 115 * @param ops The key operations, {@code null} if not specified. 116 * @param alg The intended JOSE algorithm for the key, {@code null} if 117 * not specified. 118 * @param kid The key ID, {@code null} if not specified. 119 * @param x5u The X.509 certificate URL, {@code null} if not specified. 120 * @param x5t The X.509 certificate thumbprint, {@code null} if not 121 * specified. 122 * @param x5c The X.509 certificate chain, {@code null} if not 123 * specified. 124 */ 125 public JWK(final KeyType kty, 126 final KeyUse use, 127 final Set<KeyOperation> ops, 128 final Algorithm alg, 129 final String kid, 130 final URI x5u, 131 final Base64URL x5t, 132 final List<Base64> x5c) { 133 134 if (kty == null) { 135 throw new IllegalArgumentException("The key type \"kty\" parameter must not be null"); 136 } 137 138 this.kty = kty; 139 140 if (use != null && ops != null) { 141 throw new IllegalArgumentException("They key use \"use\" and key options \"key_opts\" parameters cannot be set together"); 142 } 143 144 this.use = use; 145 this.ops = ops; 146 147 this.alg = alg; 148 this.kid = kid; 149 150 this.x5u = x5u; 151 this.x5t = x5t; 152 this.x5c = x5c; 153 } 154 155 156 /** 157 * Gets the type ({@code kty}) of this JWK. 158 * 159 * @return The key type. 160 */ 161 public KeyType getKeyType() { 162 163 return kty; 164 } 165 166 167 /** 168 * Gets the use ({@code use}) of this JWK. 169 * 170 * @return The key use, {@code null} if not specified or if the key is 171 * intended for signing as well as encryption. 172 */ 173 public KeyUse getKeyUse() { 174 175 return use; 176 } 177 178 179 /** 180 * Gets the operations ({@code key_ops}) for this JWK. 181 * 182 * @return The key operations, {@code null} if not specified. 183 */ 184 public Set<KeyOperation> getKeyOperations() { 185 186 return ops; 187 } 188 189 190 /** 191 * Gets the intended JOSE algorithm ({@code alg}) for this JWK. 192 * 193 * @return The intended JOSE algorithm, {@code null} if not specified. 194 */ 195 public Algorithm getAlgorithm() { 196 197 return alg; 198 } 199 200 201 /** 202 * Gets the ID ({@code kid}) of this JWK. The key ID can be used to 203 * match a specific key. This can be used, for instance, to choose a 204 * key within a {@link JWKSet} during key rollover. The key ID may also 205 * correspond to a JWS/JWE {@code kid} header parameter value. 206 * 207 * @return The key ID, {@code null} if not specified. 208 */ 209 public String getKeyID() { 210 211 return kid; 212 } 213 214 215 /** 216 * Gets the X.509 certificate URL ({@code x5u}) of this JWK. 217 * 218 * @return The X.509 certificate URL, {@code null} if not specified. 219 */ 220 public URI getX509CertURL() { 221 222 return x5u; 223 } 224 225 226 /** 227 * Gets the X.509 certificate thumbprint ({@code x5t}) of this JWK. 228 * 229 * @return The X.509 certificate thumbprint, {@code null} if not 230 * specified. 231 */ 232 public Base64URL getX509CertThumbprint() { 233 234 return x5t; 235 } 236 237 238 /** 239 * Gets the X.509 certificate chain ({@code x5c}) of this JWK. 240 * 241 * @return The X.509 certificate chain as a unmodifiable list, 242 * {@code null} if not specified. 243 */ 244 public List<Base64> getX509CertChain() { 245 246 if (x5c == null) { 247 return null; 248 } 249 250 return Collections.unmodifiableList(x5c); 251 } 252 253 254 /** 255 * Returns the required JWK parameters. Intended as input for JWK 256 * thumbprint computation. See RFC 7638 for more information. 257 * 258 * @return The required JWK parameters, sorted alphanumerically by key 259 * name and ready for JSON serialisation. 260 */ 261 public abstract LinkedHashMap<String,?> getRequiredParams(); 262 263 264 /** 265 * Computes the SHA-256 thumbprint of this JWK. See RFC 7638 for more 266 * information. 267 * 268 * @return The SHA-256 thumbprint. 269 * 270 * @throws JOSEException If the SHA-256 hash algorithm is not 271 * supported. 272 */ 273 public Base64URL computeThumbprint() 274 throws JOSEException { 275 276 return computeThumbprint("SHA-256"); 277 } 278 279 280 /** 281 * Computes the thumbprint of this JWK using the specified hash 282 * algorithm. See RFC 7638 for more information. 283 * 284 * @param hashAlg The hash algorithm. Must not be {@code null}. 285 * 286 * @return The SHA-256 thumbprint. 287 * 288 * @throws JOSEException If the hash algorithm is not supported. 289 */ 290 public Base64URL computeThumbprint(final String hashAlg) 291 throws JOSEException { 292 293 return ThumbprintUtils.compute(hashAlg, this); 294 } 295 296 297 /** 298 * Returns {@code true} if this JWK contains private or sensitive 299 * (non-public) parameters. 300 * 301 * @return {@code true} if this JWK contains private parameters, else 302 * {@code false}. 303 */ 304 public abstract boolean isPrivate(); 305 306 307 /** 308 * Creates a copy of this JWK with all private or sensitive parameters 309 * removed. 310 * 311 * @return The newly created public JWK, or {@code null} if none can be 312 * created. 313 */ 314 public abstract JWK toPublicJWK(); 315 316 317 /** 318 * Returns the size of this JWK. 319 * 320 * @return The JWK size, in bits. 321 */ 322 public abstract int size(); 323 324 325 /** 326 * Returns a JSON object representation of this JWK. This method is 327 * intended to be called from extending classes. 328 * 329 * <p>Example: 330 * 331 * <pre> 332 * { 333 * "kty" : "RSA", 334 * "use" : "sig", 335 * "kid" : "fd28e025-8d24-48bc-a51a-e2ffc8bc274b" 336 * } 337 * </pre> 338 * 339 * @return The JSON object representation. 340 */ 341 public JSONObject toJSONObject() { 342 343 JSONObject o = new JSONObject(); 344 345 o.put("kty", kty.getValue()); 346 347 if (use != null) { 348 o.put("use", use.identifier()); 349 } 350 351 if (ops != null) { 352 353 List<String> sl = new ArrayList<>(ops.size()); 354 355 for (KeyOperation op: ops) { 356 sl.add(op.identifier()); 357 } 358 359 o.put("key_ops", sl); 360 } 361 362 if (alg != null) { 363 o.put("alg", alg.getName()); 364 } 365 366 if (kid != null) { 367 o.put("kid", kid); 368 } 369 370 if (x5u != null) { 371 o.put("x5u", x5u.toString()); 372 } 373 374 if (x5t != null) { 375 o.put("x5t", x5t.toString()); 376 } 377 378 if (x5c != null) { 379 o.put("x5c", x5c); 380 } 381 382 return o; 383 } 384 385 386 /** 387 * Returns the JSON object string representation of this JWK. 388 * 389 * @return The JSON object string representation. 390 */ 391 @Override 392 public String toJSONString() { 393 394 return toJSONObject().toString(); 395 } 396 397 398 /** 399 * @see #toJSONString 400 */ 401 @Override 402 public String toString() { 403 404 return toJSONObject().toString(); 405 } 406 407 408 /** 409 * Parses a JWK from the specified JSON object string representation. 410 * The JWK must be an {@link ECKey}, an {@link RSAKey}, or a 411 * {@link OctetSequenceKey}. 412 * 413 * @param s The JSON object string to parse. Must not be {@code null}. 414 * 415 * @return The JWK. 416 * 417 * @throws ParseException If the string couldn't be parsed to a 418 * supported JWK. 419 */ 420 public static JWK parse(final String s) 421 throws ParseException { 422 423 return parse(JSONObjectUtils.parse(s)); 424 } 425 426 427 /** 428 * Parses a JWK from the specified JSON object representation. The JWK 429 * must be an {@link ECKey}, an {@link RSAKey}, or a 430 * {@link OctetSequenceKey}. 431 * 432 * @param jsonObject The JSON object to parse. Must not be 433 * {@code null}. 434 * 435 * @return The JWK. 436 * 437 * @throws ParseException If the JSON object couldn't be parsed to a 438 * supported JWK. 439 */ 440 public static JWK parse(final JSONObject jsonObject) 441 throws ParseException { 442 443 KeyType kty = KeyType.parse(JSONObjectUtils.getString(jsonObject, "kty")); 444 445 if (kty == KeyType.EC) { 446 447 return ECKey.parse(jsonObject); 448 449 } else if (kty == KeyType.RSA) { 450 451 return RSAKey.parse(jsonObject); 452 453 } else if (kty == KeyType.OCT) { 454 455 return OctetSequenceKey.parse(jsonObject); 456 457 } else { 458 459 throw new ParseException("Unsupported key type \"kty\" parameter: " + kty, 0); 460 } 461 } 462}