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    }