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-03-19)
047 */
048public 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 additional custom members of this JSON Web Key (JWK) set.
135         *
136         * @return The additional custom members, empty map if none.
137         */
138        public Map<String,Object> getAdditionalMembers() {
139
140                return customMembers;
141        }
142
143
144        /**
145         * Returns the JSON object representation of this JSON Web Key (JWK) 
146         * set. Sensitive non-public parameters, such as EC and RSA private key 
147         * parameters or symmetric key values, will not be included in the 
148         * output JWK members. Use the alternative 
149         * {@link #toJSONObject(boolean)} method if you wish to include them.
150         *
151         * @return The JSON object representation.
152         */
153        public JSONObject toJSONObject() {
154
155                return toJSONObject(true);
156        }
157
158
159        /**
160         * Returns the JSON object representation of this JSON Web Key (JWK) 
161         * set.
162         *
163         * @param publicParamsOnly Controls the inclusion of sensitive 
164         *                         non-public key parameters into the output 
165         *                         JWK members. If {@code true} sensitive and
166         *                         private parameters, such as private EC and
167         *                         RSA key details and symmetric secret values,
168         *                         will be omitted. If {@code false} all
169         *                         available key parameters will be included.
170         *
171         * @return The JSON object representation.
172         */
173        public JSONObject toJSONObject(final boolean publicParamsOnly) {
174
175                JSONObject o = new JSONObject(customMembers);
176
177                JSONArray a = new JSONArray();
178
179                for (JWK key: keys) {
180
181                        if (publicParamsOnly) {
182
183                                // Remove any sensitive params, then serialise
184                                a.add(key.toPublicJWK().toJSONObject());
185
186                        } else {
187
188                                a.add(key.toJSONObject());
189                        }
190                }
191
192                o.put("keys", a);
193
194                return o;
195        }
196
197
198        /**
199         * Returns the JSON object string representation of this JSON Web Key
200         * (JWK) set.
201         *
202         * @return The JSON object string representation.
203         */
204        @Override
205        public String toString() {
206
207                return toJSONObject().toString();
208        }
209
210
211        /**
212         * Parses the specified string representing a JSON Web Key (JWK) set.
213         *
214         * @param s The string to parse. Must not be {@code null}.
215         *
216         * @return The JSON Web Key (JWK) set.
217         *
218         * @throws ParseException If the string couldn't be parsed to a valid
219         *                        JSON Web Key (JWK) set.
220         */
221        public static JWKSet parse(final String s)
222                throws ParseException {
223
224                return parse(JSONObjectUtils.parseJSONObject(s));
225        }
226
227
228        /**
229         * Parses the specified JSON object representing a JSON Web Key (JWK) 
230         * set.
231         *
232         * @param json The JSON object to parse. Must not be {@code null}.
233         *
234         * @return The JSON Web Key (JWK) set.
235         *
236         * @throws ParseException If the string couldn't be parsed to a valid
237         *                        JSON Web Key (JWK) set.
238         */
239        public static JWKSet parse(final JSONObject json)
240                throws ParseException {
241
242                JSONArray keyArray = JSONObjectUtils.getJSONArray(json, "keys");
243
244                List<JWK> keys = new LinkedList<JWK>();
245
246                for (int i=0; i < keyArray.size(); i++) {
247
248                        if (! (keyArray.get(i) instanceof JSONObject)) {
249                                throw new ParseException("The \"keys\" JSON array must contain JSON objects only", 0);
250                        }
251
252                        JSONObject keyJSON = (JSONObject)keyArray.get(i);
253
254                        try {
255                                keys.add(JWK.parse(keyJSON));
256
257                        } catch (ParseException e) {
258
259                                throw new ParseException("Invalid JWK at position " + i + ": " + e.getMessage(), 0);
260                        }
261                }
262
263                // Parse additional custom members
264                JWKSet jwkSet = new JWKSet(keys);
265
266                for (Map.Entry<String,Object> entry: json.entrySet()) {
267
268                        if (entry.getKey() == null || entry.getKey().equals("keys")) {
269                                continue;
270                        }
271
272                        jwkSet.getAdditionalMembers().put(entry.getKey(), entry.getValue());
273                }
274
275                return jwkSet;
276        }
277}