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