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.proc.*;
013import com.nimbusds.jwt.*;
014
015
016/**
017 * Default processor of received {@link com.nimbusds.jwt.JWT JSON Web Token}s.
018 *
019 * <p>Must be supplied with a {@link JWSKeySelector JWS key selector} to
020 * determine the key candidate(s) for the signature verification. The exact key
021 * selection procedure is application-specific and may involve key ID lookup, a
022 * certificate check and / or other information supplied in the message
023 * {@link SecurityContext context}.
024 *
025 * <p>Similarly, the processor must be supplied with a {@link JWEKeySelector
026 * JWE key selector} if JWE messages are expected to be processed.
027 *
028 * <p>See sections 6 of RFC 7515 (JWS) and RFC 7516 (JWE) for guidelines on key
029 * selection.
030 *
031 * <p>This processor comes with the default {@link DefaultJWSVerifierFactory
032 * JWS verifier factory} and the default {@link DefaultJWEDecrypterFactory
033 * JWE decrypter factory}; they can construct verifiers / decrypters for all
034 * standard JOSE algorithms implemented by the library.
035 *
036 * <p>Note that for security reasons this processor is hardwired to reject
037 * unsecured (plain) JWTs. Override the {@link #process(PlainJWT, SecurityContext)}
038 * if you need to handle plain JWTs as well.
039 *
040 * <p>An optional {@link JWTClaimsVerifier JWT claims verifier} may be set to
041 * perform various application-specific JWT claims checks, such as issuer
042 * acceptance, after successful JWS verification / JWE decryption.
043 *
044 * <p>To process generic JOSE objects (with arbitrary payloads) use the
045 * {@link com.nimbusds.jose.proc.DefaultJOSEProcessor} class.
046 *
047 * @author Vladimir Dzhuvinov
048 * @version 2015-07-01
049 */
050public class DefaultJWTProcessor<C extends SecurityContext>
051        extends BaseJOSEProcessor<C>
052        implements JWTProcessor<ReadOnlyJWTClaimsSet, C> {
053
054
055        /**
056         * Optional claims verifier.
057         */
058        private JWTClaimsVerifier claimsVerifier;
059
060
061        /**
062         * Gets the optional JWT claims verifier. Intended to perform various
063         * application-specific JWT claims checks, such as issuer acceptance,
064         * after successful JWS verification / JWE decryption.
065         *
066         * @return The JWT claims verifier, {@code null} if not specified.
067         */
068        public JWTClaimsVerifier getJWTClaimsVerifier() {
069
070                return claimsVerifier;
071        }
072
073
074        /**
075         * Sets the optional JWT claims verifier. Intended to perform various
076         * application-specific JWT claims checks, such as issuer acceptance,
077         * after successful JWS verification / JWE decryption.
078         *
079         * @param claimsVerifier The JWT claims verifier, {@code null} if not
080         *                       specified.
081         */
082        public void setJWTClaimsVerifier(final JWTClaimsVerifier claimsVerifier) {
083
084                this.claimsVerifier = claimsVerifier;
085        }
086
087
088        /**
089         * Verifies the claims of the specified JWT.
090         *
091         * @param jwt The JWT. Must be in a state which allows the claims to
092         *            be extracted.
093         *
094         * @return The JWT claims set.
095         *
096         * @throws BadJWTException If the JWT claims are invalid or rejected.
097         */
098        private ReadOnlyJWTClaimsSet verifyAndReturnClaims(final JWT jwt)
099                throws BadJWTException {
100
101                ReadOnlyJWTClaimsSet claimsSet;
102
103                try {
104                        claimsSet = jwt.getJWTClaimsSet();
105
106                } catch (ParseException e) {
107                        // Payload not a JSON object
108                        throw new BadJWTException(e.getMessage(), e);
109                }
110
111                if (claimsVerifier != null) {
112                        claimsVerifier.verify(claimsSet);
113                }
114
115                return claimsSet;
116        }
117
118
119        @Override
120        public ReadOnlyJWTClaimsSet process(final String jwtString, final C context)
121                throws ParseException, BadJOSEException, JOSEException {
122
123                return process(JWTParser.parse(jwtString), context);
124        }
125
126
127        @Override
128        public ReadOnlyJWTClaimsSet process(final JWT jwt, final C context)
129                throws BadJOSEException, JOSEException {
130
131                if (jwt instanceof SignedJWT) {
132                        return process((SignedJWT)jwt, context);
133                }
134
135                if (jwt instanceof EncryptedJWT) {
136                        return process((EncryptedJWT)jwt, context);
137                }
138
139                if (jwt instanceof PlainJWT) {
140                        return process((PlainJWT)jwt, context);
141                }
142
143                // Should never happen
144                throw new JOSEException("Unexpected JWT object type: " + jwt.getClass());
145        }
146
147
148        @Override
149        public ReadOnlyJWTClaimsSet process(final PlainJWT plainJWT, final C context)
150                throws BadJOSEException, JOSEException {
151
152                verifyAndReturnClaims(plainJWT); // just check claims, no return
153
154                throw new BadJOSEException("Unsecured (plain) JWTs are rejected, extend class to handle");
155        }
156
157
158        @Override
159        public ReadOnlyJWTClaimsSet process(final SignedJWT signedJWT, final C context)
160                throws BadJOSEException, JOSEException {
161
162                if (getJWSKeySelector() == null) {
163                        // JWS key selector may have been deliberately omitted
164                        throw new BadJOSEException("Signed JWT rejected: No JWS key selector is configured");
165                }
166
167                if (getJWSVerifierFactory() == null) {
168                        throw new JOSEException("No JWS verifier is configured");
169                }
170
171                List<? extends Key> keyCandidates = getJWSKeySelector().selectJWSKeys(signedJWT.getHeader(), context);
172
173                if (keyCandidates == null || keyCandidates.isEmpty()) {
174                        throw new BadJOSEException("Signed JWT rejected: No matching key(s) found");
175                }
176
177                ListIterator<? extends Key> it = keyCandidates.listIterator();
178
179                while (it.hasNext()) {
180
181                        JWSVerifier verifier = getJWSVerifierFactory().createJWSVerifier(signedJWT.getHeader(), it.next());
182
183                        if (verifier == null) {
184                                continue;
185                        }
186
187                        final boolean validSignature = signedJWT.verify(verifier);
188
189                        if (validSignature) {
190                                return verifyAndReturnClaims(signedJWT);
191                        }
192
193                        if (! it.hasNext()) {
194                                // No more keys to try out
195                                throw new BadJWSException("Signed JWT rejected: Invalid signature");
196                        }
197                }
198
199                throw new BadJOSEException("JWS object rejected: No matching verifier(s) found");
200        }
201
202
203        @Override
204        public ReadOnlyJWTClaimsSet process(final EncryptedJWT encryptedJWT, final C context)
205                throws BadJOSEException, JOSEException {
206
207                if (getJWEKeySelector() == null) {
208                        // JWE key selector may have been deliberately omitted
209                        throw new BadJOSEException("Encrypted JWT rejected: No JWE key selector is configured");
210                }
211
212                if (getJWEDecrypterFactory() == null) {
213                        throw new JOSEException("No JWE decrypter is configured");
214                }
215
216                List<? extends Key> keyCandidates = getJWEKeySelector().selectJWEKeys(encryptedJWT.getHeader(), context);
217
218                if (keyCandidates == null || keyCandidates.isEmpty()) {
219                        throw new BadJOSEException("Encrypted JWT rejected: No matching key(s) found");
220                }
221
222                ListIterator<? extends Key> it = keyCandidates.listIterator();
223
224                while (it.hasNext()) {
225
226                        JWEDecrypter decrypter = getJWEDecrypterFactory().createJWEDecrypter(encryptedJWT.getHeader(), it.next());
227
228                        if (decrypter == null) {
229                                continue;
230                        }
231
232                        try {
233                                encryptedJWT.decrypt(decrypter);
234
235                        } catch (JOSEException e) {
236
237                                if (it.hasNext()) {
238                                        // Try next key
239                                        continue;
240                                }
241
242                                // No more keys to try
243                                throw new BadJWEException("Encrypted JWT rejected: " + e.getMessage(), e);
244                        }
245
246                        if ("JWT".equalsIgnoreCase(encryptedJWT.getHeader().getContentType())) {
247
248                                // Handle nested signed JWT, see http://tools.ietf.org/html/rfc7519#section-5.2
249                                SignedJWT nestedJWT = encryptedJWT.getPayload().toSignedJWT();
250
251                                if (nestedJWT == null) {
252                                        // Cannot parse payload to signed JWT
253                                        throw new BadJWTException("The payload is not a nested JWT");
254                                }
255
256                                return process(nestedJWT, context);
257                        }
258
259                        return verifyAndReturnClaims(encryptedJWT);
260                }
261
262                throw new BadJOSEException("Encrypted JWT rejected: No matching decrypter(s) found");
263        }
264}