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    }