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}