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}