001package com.nimbusds.jose.proc;
002
003
004import java.net.URI;
005import java.util.Arrays;
006import java.util.HashSet;
007import java.util.Set;
008
009import com.nimbusds.jose.*;
010
011
012/**
013 * JOSE object / header matcher. May be used to ensure a JOSE object / header
014 * matches a set of application-specific criteria.
015 *
016 * <p>Supported matching criteria:
017 *
018 * <ul>
019 *     <li>Any, one or more JOSE classes (plain, JWS, JWE).
020 *     <li>Any, one or more algorithms (alg).
021 *     <li>Any, one or more encryption methods (enc).
022 *     <li>Any, one or more JWK URLs (jku).
023 *     <li>Any, one or more JWK IDs (kid).
024 * </ul>
025 *
026 * <p>Matching by X.509 certificate URL, thumbprint and chain is not supported.
027 *
028 * @author Vladimir Dzhuvinov
029 * @version 2015-04-22
030 */
031public class JOSEMatcher {
032
033
034        /**
035         * The JOSE classes to match.
036         */
037        private final Set<Class<? extends JOSEObject>> classes;
038
039
040        /**
041         * The JOSE algorithms to match.
042         */
043        private final Set<Algorithm> algs;
044
045
046        /**
047         * The JOSE encryption methods to match (applies to JWE only).
048         */
049        private final Set<EncryptionMethod> encs;
050
051
052        /**
053         * The JWK URLs (jku) to match.
054         */
055        private final Set<URI> jkus;
056
057
058        /**
059         * The JWK IDs (kid) to match.
060         */
061        private final Set<String> kids;
062
063
064        /**
065         * Builder for constructing JOSE matchers.
066         *
067         * <p>Example use:
068         *
069         * <pre>
070         * JOSEMatcher matcher = new JOSEMatcher().keyID("123").build();
071         * </pre>
072         */
073        public static class Builder {
074
075
076                /**
077                 * The JOSE classes to match.
078                 */
079                private Set<Class<? extends JOSEObject>> classes;
080
081
082                /**
083                 * The JOSE algorithms to match.
084                 */
085                private Set<Algorithm> algs;
086
087
088                /**
089                 * The JOSE encryption methods to match (applies to JWE only).
090                 */
091                private Set<EncryptionMethod> encs;
092
093
094                /**
095                 * The JWK URLs (jku) to match.
096                 */
097                private Set<URI> jkus;
098
099
100                /**
101                 * The JWK IDs (kid) to match.
102                 */
103                private Set<String> kids;
104
105
106                /**
107                 * Sets a single JOSE class to match.
108                 *
109                 * @param clazz The JOSE class to match, {@code null} if not
110                 *              specified.
111                 *
112                 * @return This builder.
113                 */
114                public Builder joseClass(final Class<? extends JOSEObject> clazz) {
115
116                        if (clazz == null) {
117                                this.classes = null;
118                        } else {
119                                this.classes = new HashSet<Class<? extends JOSEObject>>(Arrays.asList(clazz));
120                        }
121                        return this;
122                }
123
124
125                /**
126                 * Sets multiple JOSE classes to match.
127                 *
128                 * @param classes The JOSE classes to match.
129                 *
130                 * @return This builder.
131                 */
132                public Builder joseClasses(final Class<? extends JOSEObject>... classes) {
133
134                        joseClasses(new HashSet<>(Arrays.asList(classes)));
135                        return this;
136                }
137
138
139                /**
140                 * Sets multiple JOSE classes to match.
141                 *
142                 * @param classes The JOSE classes to match, {@code null} if
143                 *                not specified.
144                 *
145                 * @return This builder.
146                 */
147                public Builder joseClasses(final Set<Class<? extends JOSEObject>> classes) {
148
149                        this.classes = classes;
150                        return this;
151                }
152
153
154                /**
155                 * Sets a single JOSE algorithm to match.
156                 *
157                 * @param alg The JOSE algorithm, {@code null} if not
158                 *            specified.
159                 *
160                 * @return This builder.
161                 */
162                public Builder algorithm(final Algorithm alg) {
163
164                        if (alg == null) {
165                                algs = null;
166                        } else {
167                                algs = new HashSet<>(Arrays.asList(alg));
168                        }
169                        return this;
170                }
171
172
173                /**
174                 * Sets multiple JOSE algorithms to match.
175                 *
176                 * @param algs The JOSE algorithms.
177                 *
178                 * @return This builder.
179                 */
180                public Builder algorithms(final Algorithm ... algs) {
181
182                        algorithms(new HashSet<>(Arrays.asList(algs)));
183                        return this;
184                }
185
186
187                /**
188                 * Sets multiple JOSE algorithms to match.
189                 *
190                 * @param algs The JOSE algorithms, {@code null} if not
191                 *             specified.
192                 *
193                 * @return This builder.
194                 */
195                public Builder algorithms(final Set<Algorithm> algs) {
196
197                        this.algs = algs;
198                        return this;
199                }
200
201
202                /**
203                 * Sets a single JOSE encryption method to match.
204                 *
205                 * @param enc The JOSE encryption methods, {@code null} if not
206                 *            specified.
207                 *
208                 * @return This builder.
209                 */
210                public Builder encryptionMethod(final EncryptionMethod enc) {
211
212                        if (enc == null) {
213                                encs = null;
214                        } else {
215                                encs = new HashSet<>(Arrays.asList(enc));
216                        }
217                        return this;
218                }
219
220
221                /**
222                 * Sets multiple JOSE encryption methods to match.
223                 *
224                 * @param encs The JOSE encryption methods.
225                 *
226                 * @return This builder.
227                 */
228                public Builder encryptionMethods(final EncryptionMethod... encs) {
229
230                        encryptionMethods(new HashSet<>(Arrays.asList(encs)));
231                        return this;
232                }
233
234
235                /**
236                 * Sets multiple JOSE encryption methods to match.
237                 *
238                 * @param encs The JOSE encryption methods, {@code null} if not
239                 *             specified.
240                 *
241                 * @return This builder.
242                 */
243                public Builder encryptionMethods(final Set<EncryptionMethod> encs) {
244
245                        this.encs = encs;
246                        return this;
247                }
248
249
250                /**
251                 * Sets a single JWK URL to match.
252                 *
253                 * @param jku The JWK URL, {@code null} if not specified.
254                 *
255                 * @return This builder.
256                 */
257                public Builder jwkURL(final URI jku) {
258
259                        if (jku == null) {
260                                jkus = null;
261                        } else {
262                                jkus = new HashSet<>(Arrays.asList(jku));
263                        }
264                        return this;
265                }
266
267
268                /**
269                 * Sets multiple JWK URLs to match.
270                 *
271                 * @param jkus The JWK URLs.
272                 *
273                 * @return This builder.
274                 */
275                public Builder jwkURLs(final URI... jkus) {
276
277                        jwkURLs(new HashSet<>(Arrays.asList(jkus)));
278                        return this;
279                }
280
281
282                /**
283                 * Sets multiple JWK URLs to match.
284                 *
285                 * @param jkus The JWK URLs, {@code null} if not specified.
286                 *
287                 * @return This builder.
288                 */
289                public Builder jwkURLs(final Set<URI> jkus) {
290
291                        this.jkus = jkus;
292                        return this;
293                }
294
295
296                /**
297                 * Sets a single key ID to match.
298                 *
299                 * @param kid The key ID, {@code null} if not specified.
300                 *
301                 * @return This builder.
302                 */
303                public Builder keyID(final String kid) {
304
305                        if (kid == null) {
306                                kids = null;
307                        } else {
308                                kids = new HashSet<>(Arrays.asList(kid));
309                        }
310                        return this;
311                }
312
313
314                /**
315                 * Sets multiple key IDs to match.
316                 *
317                 * @param ids The key IDs.
318                 *
319                 * @return This builder.
320                 */
321                public Builder keyIDs(final String ... ids) {
322
323                        keyIDs(new HashSet<>(Arrays.asList(ids)));
324                        return this;
325                }
326
327
328                /**
329                 * Sets multiple key IDs to match.
330                 *
331                 * @param kids The key IDs, {@code null} if not specified.
332                 *
333                 * @return This builder.
334                 */
335                public Builder keyIDs(final Set<String> kids) {
336
337                        this.kids = kids;
338                        return this;
339                }
340
341
342                /**
343                 * Builds a new JOSE matcher.
344                 *
345                 * @return The JOSE matcher.
346                 */
347                public JOSEMatcher build() {
348
349                        return new JOSEMatcher(classes, algs, encs, jkus, kids);
350                }
351        }
352
353
354        /**
355         * Creates a new JOSE matcher.
356         *
357         * @param classes The JOSE classes to match, {@code null} if not
358         *                specified.
359         * @param algs    The JOSE algorithms to match, {@code null} if not
360         *                specified.
361         * @param encs    The JOSE encryption methods to match, {@code null} if
362         *                not specified.
363         * @param jkus    The JWK URLs to match, {@code null} if not specified.
364         * @param kids    The key IDs to match, {@code null} if not specified.
365         */
366        public JOSEMatcher(final Set<Class<? extends JOSEObject>> classes,
367                           final Set<Algorithm> algs,
368                           final Set<EncryptionMethod> encs,
369                           final Set<URI> jkus,
370                           final Set<String> kids) {
371
372                this.classes = classes;
373                this.algs = algs;
374                this.encs = encs;
375                this.jkus = jkus;
376                this.kids = kids;
377        }
378
379
380        /**
381         * Returns the JOSE classes to match.
382         *
383         * @return The JOSE classes, {@code null} if not specified.
384         */
385        public Set<Class<? extends JOSEObject>> getJOSEClasses() {
386
387                return classes;
388        }
389
390
391        /**
392         * Returns the JOSE algorithms to match.
393         *
394         * @return The JOSE algorithms, {@code null} if not specified.
395         */
396        public Set<Algorithm> getAlgorithms() {
397
398                return algs;
399        }
400
401
402        /**
403         * Returns the JOSE encryption methods to match.
404         *
405         * @return The JOSE encryption methods, {@code null} if not specified.
406         */
407        public Set<EncryptionMethod> getEncryptionMethods() {
408
409                return encs;
410        }
411
412
413        /**
414         * Returns the JWK URLs to match.
415         *
416         * @return The JWK URLs, {@code null} if not specified.
417         */
418        public Set<URI> getJWKURLs() {
419
420                return jkus;
421        }
422
423
424        /**
425         * Returns the key IDs to match.
426         *
427         * @return The key IDs, {@code null} if not specified.
428         */
429        public Set<String> getKeyIDs() {
430
431                return kids;
432        }
433
434
435        /**
436         * Returns {@code true} if the specified JOSE object matches.
437         *
438         * @param joseObject The JOSE object. Must not  be {@code null}.
439         *
440         * @return {@code true} if the JOSE object matches, else {@code false}.
441         */
442        public boolean matches(final JOSEObject joseObject) {
443
444                if (classes != null) {
445
446                        boolean pass = false;
447                        for (Class<? extends JOSEObject> c: classes) {
448                                if (c != null && c.isInstance(joseObject)) {
449                                        pass = true;
450                                }
451                        }
452
453                        if (!pass) {
454                                return false;
455                        }
456                }
457
458                if (algs != null && ! algs.contains(joseObject.getHeader().getAlgorithm()))
459                        return false;
460
461                if (encs != null) {
462
463                        if (! (joseObject instanceof JWEObject))
464                                return false;
465
466                        JWEObject jweObject = (JWEObject)joseObject;
467
468                        if (! encs.contains(jweObject.getHeader().getEncryptionMethod()))
469                                return false;
470                }
471
472                if (jkus != null) {
473
474                        final URI jku;
475
476                        if (joseObject instanceof JWSObject) {
477                                jku = ((JWSObject) joseObject).getHeader().getJWKURL();
478                        } else if (joseObject instanceof JWEObject) {
479                                jku = ((JWEObject) joseObject).getHeader().getJWKURL();
480                        } else {
481                                // Plain object
482                                jku = null; // jku not supported by unsecured JOSE objects
483                        }
484
485                        if (! jkus.contains(jku))
486                                return false;
487                }
488
489                if (kids != null) {
490
491                        final String kid;
492
493                        if (joseObject instanceof JWSObject) {
494                                kid = ((JWSObject) joseObject).getHeader().getKeyID();
495                        } else if (joseObject instanceof JWEObject) {
496                                kid = ((JWEObject) joseObject).getHeader().getKeyID();
497                        } else {
498                                // Plain object
499                                kid = null; // kid not supported by unsecured JOSE objects
500                        }
501
502                        if (! kids.contains(kid))
503                                return false;
504                }
505
506                return true;
507        }
508}