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