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 javax.mail.internet.ContentType; 011 import javax.mail.internet.ParameterList; 012 013 import net.minidev.json.JSONArray; 014 import net.minidev.json.JSONObject; 015 016 import 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-03-29) 050 */ 051 public 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 the JSON object representation of this JSON Web Key (JWK) 189 * set. Sensitive non-public parameters, such as EC and RSA private key 190 * parameters or symmetric key values, will not be included in the 191 * output JWK members. Use the alternative 192 * {@link #toJSONObject(boolean)} method if you wish to include them. 193 * 194 * @return The JSON object representation. 195 */ 196 public JSONObject toJSONObject() { 197 198 return toJSONObject(true); 199 } 200 201 202 /** 203 * Returns the JSON object representation of this JSON Web Key (JWK) 204 * set. 205 * 206 * @param publicParamsOnly Controls the inclusion of sensitive 207 * non-public key parameters into the output 208 * JWK members. If {@code true} sensitive and 209 * private parameters, such as private EC and 210 * RSA key details and symmetric secret values, 211 * will be omitted. If {@code false} all 212 * available key parameters will be included. 213 * 214 * @return The JSON object representation. 215 */ 216 public JSONObject toJSONObject(final boolean publicParamsOnly) { 217 218 JSONObject o = new JSONObject(customMembers); 219 220 JSONArray a = new JSONArray(); 221 222 for (JWK key: keys) { 223 224 if (publicParamsOnly) { 225 226 // Remove any sensitive params, then serialise 227 a.add(key.toPublicJWK().toJSONObject()); 228 229 } else { 230 231 a.add(key.toJSONObject()); 232 } 233 } 234 235 o.put("keys", a); 236 237 return o; 238 } 239 240 241 /** 242 * Returns the JSON object string representation of this JSON Web Key 243 * (JWK) set. 244 * 245 * @return The JSON object string representation. 246 */ 247 @Override 248 public String toString() { 249 250 return toJSONObject().toString(); 251 } 252 253 254 /** 255 * Parses the specified string representing a JSON Web Key (JWK) set. 256 * 257 * @param s The string to parse. Must not be {@code null}. 258 * 259 * @return The JSON Web Key (JWK) set. 260 * 261 * @throws ParseException If the string couldn't be parsed to a valid 262 * JSON Web Key (JWK) set. 263 */ 264 public static JWKSet parse(final String s) 265 throws ParseException { 266 267 return parse(JSONObjectUtils.parseJSONObject(s)); 268 } 269 270 271 /** 272 * Parses the specified JSON object representing a JSON Web Key (JWK) 273 * set. 274 * 275 * @param json The JSON object to parse. Must not be {@code null}. 276 * 277 * @return The JSON Web Key (JWK) set. 278 * 279 * @throws ParseException If the string couldn't be parsed to a valid 280 * JSON Web Key (JWK) set. 281 */ 282 public static JWKSet parse(final JSONObject json) 283 throws ParseException { 284 285 JSONArray keyArray = JSONObjectUtils.getJSONArray(json, "keys"); 286 287 List<JWK> keys = new LinkedList<JWK>(); 288 289 for (int i=0; i < keyArray.size(); i++) { 290 291 if (! (keyArray.get(i) instanceof JSONObject)) { 292 throw new ParseException("The \"keys\" JSON array must contain JSON objects only", 0); 293 } 294 295 JSONObject keyJSON = (JSONObject)keyArray.get(i); 296 297 try { 298 keys.add(JWK.parse(keyJSON)); 299 300 } catch (ParseException e) { 301 302 throw new ParseException("Invalid JWK at position " + i + ": " + e.getMessage(), 0); 303 } 304 } 305 306 // Parse additional custom members 307 JWKSet jwkSet = new JWKSet(keys); 308 309 for (Map.Entry<String,Object> entry: json.entrySet()) { 310 311 if (entry.getKey() == null || entry.getKey().equals("keys")) { 312 continue; 313 } 314 315 jwkSet.getAdditionalMembers().put(entry.getKey(), entry.getValue()); 316 } 317 318 return jwkSet; 319 } 320 }