001 package com.nimbusds.jose.jwk; 002 003 004 import java.text.ParseException; 005 import java.util.HashMap; 006 import java.util.LinkedList; 007 import java.util.List; 008 import java.util.Map; 009 010 import net.minidev.json.JSONArray; 011 import net.minidev.json.JSONObject; 012 013 import 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-03-19) 047 */ 048 public class JWKSet { 049 050 051 /** 052 * The JWK list. 053 */ 054 private final List<JWK> keys = new LinkedList<JWK>(); 055 056 057 /** 058 * Additional custom members. 059 */ 060 private final Map<String,Object> customMembers = new HashMap<String,Object>(); 061 062 063 /** 064 * Creates a new empty JSON Web Key (JWK) set. 065 */ 066 public JWKSet() { 067 068 // Nothing to do 069 } 070 071 072 /** 073 * Creates a new JSON Web Key (JWK) set with a single key. 074 * 075 * @param key The JWK. Must not be {@code null}. 076 */ 077 public JWKSet(final JWK key) { 078 079 if (key == null) { 080 throw new IllegalArgumentException("The JWK must not be null"); 081 } 082 083 keys.add(key); 084 } 085 086 087 /** 088 * Creates a new JSON Web Key (JWK) set with the specified keys. 089 * 090 * @param keys The JWK list. Must not be {@code null}. 091 */ 092 public JWKSet(final List<JWK> keys) { 093 094 if (keys == null) { 095 throw new IllegalArgumentException("The JWK list must not be null"); 096 } 097 098 this.keys.addAll(keys); 099 } 100 101 102 /** 103 * Creates a new JSON Web Key (JWK) set with the specified keys and 104 * additional custom members. 105 * 106 * @param keys The JWK list. Must not be {@code null}. 107 * @param customMembers The additional custom members. Must not be 108 * {@code null}. 109 */ 110 public JWKSet(final List<JWK> keys, final Map<String,Object> customMembers) { 111 112 if (keys == null) { 113 throw new IllegalArgumentException("The JWK list must not be null"); 114 } 115 116 this.keys.addAll(keys); 117 118 this.customMembers.putAll(customMembers); 119 } 120 121 122 /** 123 * Gets the keys (ordered) of this JSON Web Key (JWK) set. 124 * 125 * @return The keys, empty list if none. 126 */ 127 public List<JWK> getKeys() { 128 129 return keys; 130 } 131 132 133 /** 134 * Gets the key from this JSON Web Key (JWK) set as identified by its 135 * Key ID (kid) member. 136 * 137 * <p>If more than one key exists in the JWK Set with the same 138 * identifier, this function returns only the first one in the set. 139 * 140 * @return The key identified by {@code kid} or {@code null} if no key 141 * exists. 142 */ 143 public JWK getKeyByKeyId(String kid) { 144 145 for (JWK key : getKeys()) { 146 147 if (key.getKeyID() != null && key.getKeyID().equals(kid)) { 148 return key; 149 } 150 } 151 152 // no key found 153 return null; 154 } 155 156 157 /** 158 * Gets the additional custom members of this JSON Web Key (JWK) set. 159 * 160 * @return The additional custom members, empty map if none. 161 */ 162 public Map<String,Object> getAdditionalMembers() { 163 164 return customMembers; 165 } 166 167 168 /** 169 * Returns the JSON object representation of this JSON Web Key (JWK) 170 * set. Sensitive non-public parameters, such as EC and RSA private key 171 * parameters or symmetric key values, will not be included in the 172 * output JWK members. Use the alternative 173 * {@link #toJSONObject(boolean)} method if you wish to include them. 174 * 175 * @return The JSON object representation. 176 */ 177 public JSONObject toJSONObject() { 178 179 return toJSONObject(true); 180 } 181 182 183 /** 184 * Returns the JSON object representation of this JSON Web Key (JWK) 185 * set. 186 * 187 * @param publicParamsOnly Controls the inclusion of sensitive 188 * non-public key parameters into the output 189 * JWK members. If {@code true} sensitive and 190 * private parameters, such as private EC and 191 * RSA key details and symmetric secret values, 192 * will be omitted. If {@code false} all 193 * available key parameters will be included. 194 * 195 * @return The JSON object representation. 196 */ 197 public JSONObject toJSONObject(final boolean publicParamsOnly) { 198 199 JSONObject o = new JSONObject(customMembers); 200 201 JSONArray a = new JSONArray(); 202 203 for (JWK key: keys) { 204 205 if (publicParamsOnly) { 206 207 // Remove any sensitive params, then serialise 208 a.add(key.toPublicJWK().toJSONObject()); 209 210 } else { 211 212 a.add(key.toJSONObject()); 213 } 214 } 215 216 o.put("keys", a); 217 218 return o; 219 } 220 221 222 /** 223 * Returns the JSON object string representation of this JSON Web Key 224 * (JWK) set. 225 * 226 * @return The JSON object string representation. 227 */ 228 @Override 229 public String toString() { 230 231 return toJSONObject().toString(); 232 } 233 234 235 /** 236 * Parses the specified string representing a JSON Web Key (JWK) set. 237 * 238 * @param s The string to parse. Must not be {@code null}. 239 * 240 * @return The JSON Web Key (JWK) set. 241 * 242 * @throws ParseException If the string couldn't be parsed to a valid 243 * JSON Web Key (JWK) set. 244 */ 245 public static JWKSet parse(final String s) 246 throws ParseException { 247 248 return parse(JSONObjectUtils.parseJSONObject(s)); 249 } 250 251 252 /** 253 * Parses the specified JSON object representing a JSON Web Key (JWK) 254 * set. 255 * 256 * @param json The JSON object to parse. Must not be {@code null}. 257 * 258 * @return The JSON Web Key (JWK) set. 259 * 260 * @throws ParseException If the string couldn't be parsed to a valid 261 * JSON Web Key (JWK) set. 262 */ 263 public static JWKSet parse(final JSONObject json) 264 throws ParseException { 265 266 JSONArray keyArray = JSONObjectUtils.getJSONArray(json, "keys"); 267 268 List<JWK> keys = new LinkedList<JWK>(); 269 270 for (int i=0; i < keyArray.size(); i++) { 271 272 if (! (keyArray.get(i) instanceof JSONObject)) { 273 throw new ParseException("The \"keys\" JSON array must contain JSON objects only", 0); 274 } 275 276 JSONObject keyJSON = (JSONObject)keyArray.get(i); 277 278 try { 279 keys.add(JWK.parse(keyJSON)); 280 281 } catch (ParseException e) { 282 283 throw new ParseException("Invalid JWK at position " + i + ": " + e.getMessage(), 0); 284 } 285 } 286 287 // Parse additional custom members 288 JWKSet jwkSet = new JWKSet(keys); 289 290 for (Map.Entry<String,Object> entry: json.entrySet()) { 291 292 if (entry.getKey() == null || entry.getKey().equals("keys")) { 293 continue; 294 } 295 296 jwkSet.getAdditionalMembers().put(entry.getKey(), entry.getValue()); 297 } 298 299 return jwkSet; 300 } 301 }