001    package com.nimbusds.jose;
002    
003    
004    import java.text.ParseException;
005    
006    import java.util.HashMap;
007    import java.util.LinkedList;
008    import java.util.List;
009    import java.util.Map;
010    
011    import net.minidev.json.JSONArray;
012    import net.minidev.json.JSONObject;
013    
014    import com.nimbusds.jose.util.JSONObjectUtils;
015    
016    
017    /**
018     * JSON Web Key (JWK) set. Represented by a JSON object that contains an array
019     * of {@link JWK JSON Web Keys} (JWKs) as the value of its "keys" member.
020     * Additional (custom) members of the JWK Set JSON object are also supported.
021     *
022     * <p>Example JSON Web Key (JWK) set:
023     *
024     * <pre>
025     * {
026     *   "keys" : [ { "kty" : "EC",
027     *                "crv" : "P-256",
028     *                "x"   : "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
029     *                "y"   : "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
030     *                "use" : "enc",
031     *                "kid" : "1" },
032     *
033     *              { "kty" : "RSA",
034     *                "n"   : "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx
035     *                         4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMs
036     *                         tn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2
037     *                         QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbI
038     *                         SD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqb
039     *                         w0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
040     *                "e"   : "AQAB",
041     *                "alg" : "RS256",
042     *                "kid" : "2011-04-29" } ]
043     * }
044     * </pre>
045     *
046     * @author Vladimir Dzhuvinov
047     * @version $version$ (2013-01-08)
048     */
049    public class JWKSet {
050    
051    
052            /**
053             * The JWK list.
054             */
055            private final List<JWK> keys = new LinkedList<JWK>();
056    
057    
058            /**
059             * Additional custom members.
060             */
061            private final Map<String,Object> customMembers = new HashMap<String,Object>();
062            
063            
064            /**
065             * Creates a new empty JSON Web Key (JWK) set.
066             */
067            public JWKSet() {
068            
069                    // Nothing to do
070            }
071            
072            
073            /**
074             * Creates a new JSON Web Key (JWK) set with a single key.
075             *
076             * @param key The JWK. Must not be {@code null}.
077             */
078            public JWKSet(final JWK key) {
079            
080                    if (key == null)
081                            throw new IllegalArgumentException("The JWK must not be null");
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                    this.keys.addAll(keys);
098            }
099    
100    
101            /**
102             * Creates a new JSON Web Key (JWK) set with the specified keys and
103             * additional custom members.
104             *
105             * @param keys          The JWK list. Must not be {@code null}.
106             * @param customMembers The additional custom members. Must not be
107             *                      {@code null}.
108             */
109            public JWKSet(final List<JWK> keys, final Map<String,Object> customMembers) {
110    
111                    if (keys == null)
112                            throw new IllegalArgumentException("The JWK list must not be null");
113                    
114                    this.keys.addAll(keys);
115    
116                    this.customMembers.putAll(customMembers);
117            }
118            
119            
120            /**
121             * Gets the keys (ordered) of this JSON Web Key (JWK) set.
122             *
123             * @return The keys, empty list if none.
124             */
125            public List<JWK> getKeys() {
126            
127                    return keys;
128            }
129    
130    
131            /**
132             * Gets the additional custom members of this JSON Web Key (JWK) set.
133             *
134             * @return The additional custom members, empty map if none.
135             */
136            public Map<String,Object> getAdditionalMembers() {
137    
138                    return customMembers;
139            }
140            
141            
142            /**
143             * Returns a JSON object representation of this JSON Web Key (JWK) set.
144             *
145             * @return The JSON object representation.
146             */
147            public JSONObject toJSONObject() {
148                    
149                    JSONObject o = new JSONObject(customMembers);
150    
151                    JSONArray a = new JSONArray();
152    
153                    for (final JWK key: keys) {
154    
155                            a.add(key.toJSONObject());
156                    }
157                    
158                    o.put("keys", a);
159                    
160                    return o;
161            }
162            
163    
164            /**
165             * Returns the JSON object string representation of this JSON Web Key
166             * (JWK) set.
167             *
168             * @return The JSON object string representation.
169             */
170            public String toString() {
171            
172                    return toJSONObject().toString();
173            }
174            
175            
176            /**
177             * Parses the specified string representing a JSON Web Key (JWK) set.
178             *
179             * @param s The string to parse. Must not be {@code null}.
180             *
181             * @return The JSON Web Key (JWK) set.
182             *
183             * @throws ParseException If the string couldn't be parsed to a valid
184             *                        JSON Web Key (JWK) set.
185             */
186            public static JWKSet parse(final String s)
187                    throws ParseException {
188                    
189                    return parse(JSONObjectUtils.parseJSONObject(s));
190            }
191            
192            
193            /**
194             * Parses the specified JSON object representing a JSON Web Key (JWK) 
195             * set.
196             *
197             * @param json The JSON object to parse. Must not be {@code null}.
198             *
199             * @return The JSON Web Key (JWK) set.
200             *
201             * @throws ParseException If the string couldn't be parsed to a valid
202             *                        JSON Web Key (JWK) set.
203             */
204            public static JWKSet parse(final JSONObject json)
205                    throws ParseException {
206                    
207                    JSONArray keyArray = JSONObjectUtils.getJSONArray(json, "keys");
208                    
209                    List<JWK> keys = new LinkedList<JWK>();
210                    
211                    for (int i=0; i < keyArray.size(); i++) {
212                    
213                            if (! (keyArray.get(i) instanceof JSONObject))
214                                    throw new ParseException("The \"keys\" JSON array must contain JSON objects only", 0);
215                            
216                            JSONObject keyJSON = (JSONObject)keyArray.get(i);
217                            
218                            try {
219                                    keys.add(JWK.parse(keyJSON));
220                                    
221                            } catch (ParseException e) {
222                            
223                                    throw new ParseException("Invalid JWK at position " + i + ": " + e.getMessage(), 0);
224                            }
225                    }
226    
227                    // Parse additional custom members
228                    JWKSet jwkSet = new JWKSet(keys);
229    
230                    for (Map.Entry<String,Object> entry: json.entrySet()) {
231    
232                            if (entry.getKey() == null || entry.getKey().equals("keys"))
233                                    continue;
234    
235                            jwkSet.getAdditionalMembers().put(entry.getKey(), entry.getValue());
236                    }
237                    
238                    return jwkSet;
239            }
240    }