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