001package com.nimbusds.jwt.proc;
002
003
004import java.security.Key;
005import java.text.ParseException;
006import java.util.List;
007import java.util.ListIterator;
008
009import com.nimbusds.jose.JOSEException;
010import com.nimbusds.jose.JWEDecrypter;
011import com.nimbusds.jose.JWSVerifier;
012import com.nimbusds.jose.crypto.factories.DefaultJWEDecrypterFactory;
013import com.nimbusds.jose.crypto.factories.DefaultJWSVerifierFactory;
014import com.nimbusds.jose.proc.*;
015import com.nimbusds.jwt.*;
016
017
018/**
019 * Default processor of {@link com.nimbusds.jwt.PlainJWT unsecured} (plain),
020 * {@link com.nimbusds.jwt.SignedJWT signed} and
021 * {@link com.nimbusds.jwt.EncryptedJWT encrypted} JSON Web Tokens (JWTs).
022 *
023 * <p>Must be configured with the following:
024 *
025 * <ol>
026 *     <li>To process signed JWTs: A {@link JWSKeySelector JWS key selector}
027 *     to determine the key candidate(s) for the signature verification. The
028 *     key selection procedure is application-specific and may involve key ID
029 *     lookup, a certificate check and / or other information supplied in the
030 *     message {@link SecurityContext context}.</li>
031 *
032 *     <li>To process encrypted JWTs: A {@link JWEKeySelector JWE key selector}
033 *     to determine the key candidate(s) for decryption. The key selection
034 *     procedure is application-specific and may involve key ID lookup, a
035 *     certificate check and / or other information supplied in the message
036 *     {@link SecurityContext context}.</li>
037 * </ol>
038 *
039 * <p>See sections 6 of RFC 7515 (JWS) and RFC 7516 (JWE) for guidelines on key
040 * selection.
041 *
042 * <p>This processor comes with the default {@link DefaultJWSVerifierFactory
043 * JWS verifier factory} and the default {@link DefaultJWEDecrypterFactory
044 * JWE decrypter factory}; they can construct verifiers / decrypters for all
045 * standard JOSE algorithms implemented by the library.
046 *
047 * <p>Note that for security reasons this processor is hardwired to reject
048 * unsecured (plain) JWTs. Override the {@link #process(PlainJWT, SecurityContext)}
049 * if you need to handle plain JWTs as well.
050 *
051 * <p>A {@link DefaultJWTClaimsVerifier default JWT claims verifier} is
052 * provided, to perform a minimal check of the claims after a successful JWS
053 * verification / JWE decryption. It checks the token expiration (exp) and
054 * not-before (nbf) timestamps if these are present. The default JWT claims
055 * verifier may be extended to perform additional checks, such as issuer and
056 * subject acceptance.
057 *
058 * <p>To process generic JOSE objects (with arbitrary payloads) use the
059 * {@link com.nimbusds.jose.proc.DefaultJOSEProcessor} class.
060 *
061 * @author Vladimir Dzhuvinov
062 * @version 2015-08-27
063 */
064public class DefaultJWTProcessor<C extends SecurityContext>
065        implements ConfigurableJWTProcessor<C> {
066
067
068        /**
069         * The JWS key selector.
070         */
071        private JWSKeySelector<C> jwsKeySelector;
072
073
074        /**
075         * The JWE key selector.
076         */
077        private JWEKeySelector<C> jweKeySelector;
078
079
080        /**
081         * The JWS verifier factory.
082         */
083        private JWSVerifierFactory jwsVerifierFactory = new DefaultJWSVerifierFactory();
084
085
086        /**
087         * The JWE decrypter factory.
088         */
089        private JWEDecrypterFactory jweDecrypterFactory = new DefaultJWEDecrypterFactory();
090
091
092        /**
093         * The claims verifier.
094         */
095        private JWTClaimsVerifier claimsVerifier = new DefaultJWTClaimsVerifier();
096
097
098        @Override
099        public JWSKeySelector<C> getJWSKeySelector() {
100
101                return jwsKeySelector;
102        }
103
104
105        @Override
106        public void setJWSKeySelector(final JWSKeySelector<C> jwsKeySelector) {
107
108                this.jwsKeySelector = jwsKeySelector;
109        }
110
111
112        @Override
113        public JWEKeySelector<C> getJWEKeySelector() {
114
115                return jweKeySelector;
116        }
117
118
119        @Override
120        public void setJWEKeySelector(final JWEKeySelector<C> jweKeySelector) {
121
122                this.jweKeySelector = jweKeySelector;
123        }
124
125
126        @Override
127        public JWSVerifierFactory getJWSVerifierFactory() {
128
129                return jwsVerifierFactory;
130        }
131
132
133        @Override
134        public void setJWSVerifierFactory(final JWSVerifierFactory factory) {
135
136                jwsVerifierFactory = factory;
137        }
138
139
140        @Override
141        public JWEDecrypterFactory getJWEDecrypterFactory() {
142
143                return jweDecrypterFactory;
144        }
145
146
147        @Override
148        public void setJWEDecrypterFactory(final JWEDecrypterFactory factory) {
149
150                jweDecrypterFactory = factory;
151        }
152
153
154        @Override
155        public JWTClaimsVerifier getJWTClaimsVerifier() {
156
157                return claimsVerifier;
158        }
159
160
161        @Override
162        public void setJWTClaimsVerifier(final JWTClaimsVerifier claimsVerifier) {
163
164                this.claimsVerifier = claimsVerifier;
165        }
166
167
168        /**
169         * Verifies the claims of the specified JWT.
170         *
171         * @param jwt The JWT. Must be in a state which allows the claims to
172         *            be extracted.
173         *
174         * @return The JWT claims set.
175         *
176         * @throws BadJWTException If the JWT claims are invalid or rejected.
177         */
178        private JWTClaimsSet verifyAndReturnClaims(final JWT jwt)
179                throws BadJWTException {
180
181                JWTClaimsSet claimsSet;
182
183                try {
184                        claimsSet = jwt.getJWTClaimsSet();
185
186                } catch (ParseException e) {
187                        // Payload not a JSON object
188                        throw new BadJWTException(e.getMessage(), e);
189                }
190
191                if (getJWTClaimsVerifier() != null) {
192                        getJWTClaimsVerifier().verify(claimsSet);
193                }
194
195                return claimsSet;
196        }
197
198
199        @Override
200        public JWTClaimsSet process(final String jwtString, final C context)
201                throws ParseException, BadJOSEException, JOSEException {
202
203                return process(JWTParser.parse(jwtString), context);
204        }
205
206
207        @Override
208        public JWTClaimsSet process(final JWT jwt, final C context)
209                throws BadJOSEException, JOSEException {
210
211                if (jwt instanceof SignedJWT) {
212                        return process((SignedJWT)jwt, context);
213                }
214
215                if (jwt instanceof EncryptedJWT) {
216                        return process((EncryptedJWT)jwt, context);
217                }
218
219                if (jwt instanceof PlainJWT) {
220                        return process((PlainJWT)jwt, context);
221                }
222
223                // Should never happen
224                throw new JOSEException("Unexpected JWT object type: " + jwt.getClass());
225        }
226
227
228        @Override
229        public JWTClaimsSet process(final PlainJWT plainJWT, final C context)
230                throws BadJOSEException, JOSEException {
231
232                verifyAndReturnClaims(plainJWT); // just check claims, no return
233
234                throw new BadJOSEException("Unsecured (plain) JWTs are rejected, extend class to handle");
235        }
236
237
238        @Override
239        public JWTClaimsSet process(final SignedJWT signedJWT, final C context)
240                throws BadJOSEException, JOSEException {
241
242                if (getJWSKeySelector() == null) {
243                        // JWS key selector may have been deliberately omitted
244                        throw new BadJOSEException("Signed JWT rejected: No JWS key selector is configured");
245                }
246
247                if (getJWSVerifierFactory() == null) {
248                        throw new JOSEException("No JWS verifier is configured");
249                }
250
251                List<? extends Key> keyCandidates = getJWSKeySelector().selectJWSKeys(signedJWT.getHeader(), context);
252
253                if (keyCandidates == null || keyCandidates.isEmpty()) {
254                        throw new BadJOSEException("Signed JWT rejected: No matching key(s) found");
255                }
256
257                ListIterator<? extends Key> it = keyCandidates.listIterator();
258
259                while (it.hasNext()) {
260
261                        JWSVerifier verifier = getJWSVerifierFactory().createJWSVerifier(signedJWT.getHeader(), it.next());
262
263                        if (verifier == null) {
264                                continue;
265                        }
266
267                        final boolean validSignature = signedJWT.verify(verifier);
268
269                        if (validSignature) {
270                                return verifyAndReturnClaims(signedJWT);
271                        }
272
273                        if (! it.hasNext()) {
274                                // No more keys to try out
275                                throw new BadJWSException("Signed JWT rejected: Invalid signature");
276                        }
277                }
278
279                throw new BadJOSEException("JWS object rejected: No matching verifier(s) found");
280        }
281
282
283        @Override
284        public JWTClaimsSet process(final EncryptedJWT encryptedJWT, final C context)
285                throws BadJOSEException, JOSEException {
286
287                if (getJWEKeySelector() == null) {
288                        // JWE key selector may have been deliberately omitted
289                        throw new BadJOSEException("Encrypted JWT rejected: No JWE key selector is configured");
290                }
291
292                if (getJWEDecrypterFactory() == null) {
293                        throw new JOSEException("No JWE decrypter is configured");
294                }
295
296                List<? extends Key> keyCandidates = getJWEKeySelector().selectJWEKeys(encryptedJWT.getHeader(), context);
297
298                if (keyCandidates == null || keyCandidates.isEmpty()) {
299                        throw new BadJOSEException("Encrypted JWT rejected: No matching key(s) found");
300                }
301
302                ListIterator<? extends Key> it = keyCandidates.listIterator();
303
304                while (it.hasNext()) {
305
306                        JWEDecrypter decrypter = getJWEDecrypterFactory().createJWEDecrypter(encryptedJWT.getHeader(), it.next());
307
308                        if (decrypter == null) {
309                                continue;
310                        }
311
312                        try {
313                                encryptedJWT.decrypt(decrypter);
314
315                        } catch (JOSEException e) {
316
317                                if (it.hasNext()) {
318                                        // Try next key
319                                        continue;
320                                }
321
322                                // No more keys to try
323                                throw new BadJWEException("Encrypted JWT rejected: " + e.getMessage(), e);
324                        }
325
326                        if ("JWT".equalsIgnoreCase(encryptedJWT.getHeader().getContentType())) {
327
328                                // Handle nested signed JWT, see http://tools.ietf.org/html/rfc7519#section-5.2
329                                SignedJWT nestedJWT = encryptedJWT.getPayload().toSignedJWT();
330
331                                if (nestedJWT == null) {
332                                        // Cannot parse payload to signed JWT
333                                        throw new BadJWTException("The payload is not a nested JWT");
334                                }
335
336                                return process(nestedJWT, context);
337                        }
338
339                        return verifyAndReturnClaims(encryptedJWT);
340                }
341
342                throw new BadJOSEException("Encrypted JWT rejected: No matching decrypter(s) found");
343        }
344}