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