001package com.nimbusds.jose.jwk; 002 003 004import java.text.ParseException; 005import java.util.HashMap; 006import java.util.LinkedList; 007import java.util.List; 008import java.util.Map; 009 010import javax.mail.internet.ContentType; 011import javax.mail.internet.ParameterList; 012 013import net.minidev.json.JSONArray; 014import net.minidev.json.JSONObject; 015 016import com.nimbusds.jose.util.JSONObjectUtils; 017 018 019/** 020 * JSON Web Key (JWK) set. Represented by a JSON object that contains an array 021 * of {@link JWK JSON Web Keys} (JWKs) as the value of its "keys" member. 022 * Additional (custom) members of the JWK Set JSON object are also supported. 023 * 024 * <p>Example JSON Web Key (JWK) set: 025 * 026 * <pre> 027 * { 028 * "keys" : [ { "kty" : "EC", 029 * "crv" : "P-256", 030 * "x" : "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", 031 * "y" : "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", 032 * "use" : "enc", 033 * "kid" : "1" }, 034 * 035 * { "kty" : "RSA", 036 * "n" : "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx 037 * 4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMs 038 * tn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2 039 * QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbI 040 * SD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqb 041 * w0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", 042 * "e" : "AQAB", 043 * "alg" : "RS256", 044 * "kid" : "2011-04-29" } ] 045 * } 046 * </pre> 047 * 048 * @author Vladimir Dzhuvinov 049 * @version $version$ (2013-09-20) 050 */ 051public class JWKSet { 052 053 054 /** 055 * The MIME type of JWK set objects: 056 * {@code application/jwk-set+json; charset=UTF-8} 057 */ 058 public static final ContentType MIME_TYPE; 059 060 061 static { 062 063 final ParameterList params = new ParameterList(); 064 params.set("charset", "UTF-8"); 065 066 MIME_TYPE = new ContentType("application", "jwk-set+json", params); 067 } 068 069 070 /** 071 * The JWK list. 072 */ 073 private final List<JWK> keys = new LinkedList<JWK>(); 074 075 076 /** 077 * Additional custom members. 078 */ 079 private final Map<String,Object> customMembers = new HashMap<String,Object>(); 080 081 082 /** 083 * Creates a new empty JSON Web Key (JWK) set. 084 */ 085 public JWKSet() { 086 087 // Nothing to do 088 } 089 090 091 /** 092 * Creates a new JSON Web Key (JWK) set with a single key. 093 * 094 * @param key The JWK. Must not be {@code null}. 095 */ 096 public JWKSet(final JWK key) { 097 098 if (key == null) { 099 throw new IllegalArgumentException("The JWK must not be null"); 100 } 101 102 keys.add(key); 103 } 104 105 106 /** 107 * Creates a new JSON Web Key (JWK) set with the specified keys. 108 * 109 * @param keys The JWK list. Must not be {@code null}. 110 */ 111 public JWKSet(final List<JWK> keys) { 112 113 if (keys == null) { 114 throw new IllegalArgumentException("The JWK list must not be null"); 115 } 116 117 this.keys.addAll(keys); 118 } 119 120 121 /** 122 * Creates a new JSON Web Key (JWK) set with the specified keys and 123 * additional custom members. 124 * 125 * @param keys The JWK list. Must not be {@code null}. 126 * @param customMembers The additional custom members. Must not be 127 * {@code null}. 128 */ 129 public JWKSet(final List<JWK> keys, final Map<String,Object> customMembers) { 130 131 if (keys == null) { 132 throw new IllegalArgumentException("The JWK list must not be null"); 133 } 134 135 this.keys.addAll(keys); 136 137 this.customMembers.putAll(customMembers); 138 } 139 140 141 /** 142 * Gets the keys (ordered) of this JSON Web Key (JWK) set. 143 * 144 * @return The keys, empty list if none. 145 */ 146 public List<JWK> getKeys() { 147 148 return keys; 149 } 150 151 152 /** 153 * Gets the key from this JSON Web Key (JWK) set as identified by its 154 * Key ID (kid) member. 155 * 156 * <p>If more than one key exists in the JWK Set with the same 157 * identifier, this function returns only the first one in the set. 158 * 159 * @return The key identified by {@code kid} or {@code null} if no key 160 * exists. 161 */ 162 public JWK getKeyByKeyId(String kid) { 163 164 for (JWK key : getKeys()) { 165 166 if (key.getKeyID() != null && key.getKeyID().equals(kid)) { 167 return key; 168 } 169 } 170 171 // no key found 172 return null; 173 } 174 175 176 /** 177 * Gets the additional custom members of this JSON Web Key (JWK) set. 178 * 179 * @return The additional custom members, empty map if none. 180 */ 181 public Map<String,Object> getAdditionalMembers() { 182 183 return customMembers; 184 } 185 186 187 /** 188 * Returns a copy of this JSON Web Key (JWK) set with all private keys 189 * and parameters removed. 190 * 191 * @return A copy of this JWK set with all private keys and parameters 192 * removed. 193 */ 194 public JWKSet toPublicJWKSet() { 195 196 List<JWK> publicKeyList = new LinkedList<JWK>(); 197 198 for (JWK key: keys) { 199 200 JWK publicKey = key.toPublicJWK(); 201 202 if (publicKey != null) { 203 publicKeyList.add(publicKey); 204 } 205 } 206 207 return new JWKSet(publicKeyList, customMembers); 208 } 209 210 211 /** 212 * Returns the JSON object representation of this JSON Web Key (JWK) 213 * set. Private keys and parameters will be omitted from the output. 214 * Use the alternative {@link #toJSONObject(boolean)} method if you 215 * wish to include them. 216 * 217 * @return The JSON object representation. 218 */ 219 public JSONObject toJSONObject() { 220 221 return toJSONObject(true); 222 } 223 224 225 /** 226 * Returns the JSON object representation of this JSON Web Key (JWK) 227 * set. 228 * 229 * @param publicKeysOnly Controls the inclusion of private keys and 230 * parameters into the output JWK members. If 231 * {@code true} private keys and parameters will 232 * be omitted. If {@code false} all available key 233 * parameters will be included. 234 * 235 * @return The JSON object representation. 236 */ 237 public JSONObject toJSONObject(final boolean publicKeysOnly) { 238 239 JSONObject o = new JSONObject(customMembers); 240 241 JSONArray a = new JSONArray(); 242 243 for (JWK key: keys) { 244 245 if (publicKeysOnly) { 246 247 // Try to get public key, then serialise 248 JWK publicKey = key.toPublicJWK(); 249 250 if (publicKey != null) { 251 a.add(publicKey.toJSONObject()); 252 } 253 } else { 254 255 a.add(key.toJSONObject()); 256 } 257 } 258 259 o.put("keys", a); 260 261 return o; 262 } 263 264 265 /** 266 * Returns the JSON object string representation of this JSON Web Key 267 * (JWK) set. 268 * 269 * @return The JSON object string representation. 270 */ 271 @Override 272 public String toString() { 273 274 return toJSONObject().toString(); 275 } 276 277 278 /** 279 * Parses the specified string representing a JSON Web Key (JWK) set. 280 * 281 * @param s The string to parse. Must not be {@code null}. 282 * 283 * @return The JSON Web Key (JWK) set. 284 * 285 * @throws ParseException If the string couldn't be parsed to a valid 286 * JSON Web Key (JWK) set. 287 */ 288 public static JWKSet parse(final String s) 289 throws ParseException { 290 291 return parse(JSONObjectUtils.parseJSONObject(s)); 292 } 293 294 295 /** 296 * Parses the specified JSON object representing a JSON Web Key (JWK) 297 * set. 298 * 299 * @param json The JSON object to parse. Must not be {@code null}. 300 * 301 * @return The JSON Web Key (JWK) set. 302 * 303 * @throws ParseException If the string couldn't be parsed to a valid 304 * JSON Web Key (JWK) set. 305 */ 306 public static JWKSet parse(final JSONObject json) 307 throws ParseException { 308 309 JSONArray keyArray = JSONObjectUtils.getJSONArray(json, "keys"); 310 311 List<JWK> keys = new LinkedList<JWK>(); 312 313 for (int i=0; i < keyArray.size(); i++) { 314 315 if (! (keyArray.get(i) instanceof JSONObject)) { 316 throw new ParseException("The \"keys\" JSON array must contain JSON objects only", 0); 317 } 318 319 JSONObject keyJSON = (JSONObject)keyArray.get(i); 320 321 try { 322 keys.add(JWK.parse(keyJSON)); 323 324 } catch (ParseException e) { 325 326 throw new ParseException("Invalid JWK at position " + i + ": " + e.getMessage(), 0); 327 } 328 } 329 330 // Parse additional custom members 331 JWKSet jwkSet = new JWKSet(keys); 332 333 for (Map.Entry<String,Object> entry: json.entrySet()) { 334 335 if (entry.getKey() == null || entry.getKey().equals("keys")) { 336 continue; 337 } 338 339 jwkSet.getAdditionalMembers().put(entry.getKey(), entry.getValue()); 340 } 341 342 return jwkSet; 343 } 344}