001package com.nimbusds.openid.connect.sdk.util;
002
003
004import java.text.ParseException;
005import java.util.Collection;
006import java.util.Hashtable;
007import java.util.Map;
008
009import net.jcip.annotations.ThreadSafe;
010
011import com.nimbusds.jose.JOSEException;
012import com.nimbusds.jose.JWEAlgorithm;
013import com.nimbusds.jose.JWEDecrypter;
014import com.nimbusds.jose.JWSAlgorithm;
015import com.nimbusds.jose.JWSVerifier;
016import com.nimbusds.jwt.EncryptedJWT;
017import com.nimbusds.jwt.JWT;
018import com.nimbusds.jwt.PlainJWT;
019import com.nimbusds.jwt.ReadOnlyJWTClaimsSet;
020import com.nimbusds.jwt.SignedJWT;
021
022
023/**
024 * The default decoder of JSON Web Tokens (JWTs).
025 *
026 * <p>Supports:
027 *
028 * <ul>
029 *     <li>Plaintext JWTs.
030 *     <li>JWS-signed JWTs.
031 *     <li>JWE-encrypted JWTs.
032 * </ul>
033 *
034 * <p>Not supported: JWS-signed and then JWE-encrypted (nested) objects.
035 */
036@ThreadSafe
037public class DefaultJWTDecoder implements JWTDecoder {
038
039
040        /**
041         * Thread-safe map of available JWS verifiers.
042         */
043        private final Map<JWSAlgorithm,JWSVerifier> jwsVerifiers = 
044                new Hashtable<>();
045        
046        
047        /**
048         * Thread-safe map of available JWE decrypters.
049         */
050        private final Map<JWEAlgorithm,JWEDecrypter> jweDecrypters = 
051                new Hashtable<>();
052        
053        
054        /**
055         * Creates a new decoder of JSON Web Tokens (JWTs). The decoder must 
056         * then be supplied with one or more configured JWS verifiers and / or 
057         * JWE decrypters.
058         */
059        public DefaultJWTDecoder() {
060        
061                // Nothing to do
062        }
063        
064        
065        /**
066         * Adds the specified JWS verifier for decoding signed JWTs. The JWS 
067         * algorithms accepted by the verifier should match the ones used to 
068         * secure the expected JWTs.
069         *
070         * @param verifier The JWS verifier to add. Must be ready to verify
071         *                 signed JWTs and not {@code null}.
072         */
073        public void addJWSVerifier(final JWSVerifier verifier) {
074        
075                for (JWSAlgorithm alg: verifier.getAcceptedAlgorithms()) {
076
077                        jwsVerifiers.put(alg, verifier);
078                }
079        }
080        
081        
082        /**
083         * Gets the JWS verifiers.
084         *
085         * @return The JWS verifiers, empty collection if none.
086         */
087        public Collection<JWSVerifier> getJWSVerifiers() {
088        
089                return jwsVerifiers.values();
090        }
091        
092        
093        /**
094         * Adds the specified JWE decrypter for decoding encrypted JWTs. The
095         * JWE algorithms accepted by the decrypter should match the ones
096         * used to secure the expected JWTs.
097         *
098         * @param decrypter The JWE decrypter to add. Must be ready to decrypt
099         *                  encrypted JWTs and not {@code null}.
100         */
101        public void addJWEDecrypter(final JWEDecrypter decrypter) {
102        
103                for (JWEAlgorithm alg: decrypter.getAcceptedAlgorithms()) {
104
105                        jweDecrypters.put(alg, decrypter);
106                }
107        }
108        
109        
110        /**
111         * Gets the JWE decrypters.
112         *
113         * @return The JWE decrypters, empty collection if none.
114         */
115        public Collection<JWEDecrypter> getJWEDecrypters() {
116        
117                return jweDecrypters.values();
118        }
119        
120        
121        /**
122         * Verifies a signed JWT by calling the matching verifier for its JWS
123         * algorithm.
124         *
125         * @param signedJWT The signed JWT to verify. Must not be {@code null}.
126         *
127         * @return The JWT claims set.
128         *
129         * @throws JOSEException  If no matching JWS verifier was found, the 
130         *                        signature is bad or verification failed.
131         * @throws ParseException If parsing of the JWT claims set failed.
132         */
133        private ReadOnlyJWTClaimsSet verify(final SignedJWT signedJWT)
134                throws JOSEException, ParseException {
135                
136                JWSAlgorithm alg = signedJWT.getHeader().getAlgorithm();
137                
138                JWSVerifier verifier = jwsVerifiers.get(alg);
139                
140                if (verifier == null) {
141
142                        throw new JOSEException("Unsupported JWS algorithm: " + alg);
143                }
144                        
145                
146                boolean verified;
147
148                try {
149                        verified = signedJWT.verify(verifier);
150
151                } catch (IllegalStateException e) {
152
153                        throw new JOSEException(e.getMessage(), e);
154                }
155                
156                if (! verified) {
157
158                        throw new JOSEException("Bad JWS signature");
159                }
160                
161                return signedJWT.getJWTClaimsSet();
162        }
163        
164        
165        /**
166         * Decrypts an encrypted JWT by calling the matching decrypter for its
167         * JWE algorithm and encryption method.
168         *
169         * @param encryptedJWT The encrypted JWT to decrypt. Must not be 
170         *                     {@code null}.
171         *
172         * @return The JWT claims set.
173         *
174         * @throws JOSEException  If no matching JWE decrypter was found or if
175         *                        decryption failed.
176         * @throws ParseException If parsing of the JWT claims set failed.
177         */
178        private ReadOnlyJWTClaimsSet decrypt(final EncryptedJWT encryptedJWT)
179                throws JOSEException, ParseException {
180                
181                JWEAlgorithm alg = encryptedJWT.getHeader().getAlgorithm();
182                
183                JWEDecrypter decrypter = jweDecrypters.get(alg);
184                
185                if (decrypter == null) {
186
187                        throw new JOSEException("Unsupported JWE algorithm: " + alg);
188                }
189                
190                
191                try {
192                        encryptedJWT.decrypt(decrypter);
193
194                } catch (IllegalStateException e) {
195
196                        throw new JOSEException(e.getMessage(), e);
197                }
198                
199                return encryptedJWT.getJWTClaimsSet();
200        }
201
202
203        @Override
204        public ReadOnlyJWTClaimsSet decodeJWT(final JWT jwt)
205                throws JOSEException, ParseException {
206                
207                if (jwt instanceof PlainJWT) {
208                
209                        PlainJWT plainJWT = (PlainJWT)jwt;
210                        
211                        return plainJWT.getJWTClaimsSet();
212                
213                } else if (jwt instanceof SignedJWT) {
214                
215                        SignedJWT signedJWT = (SignedJWT)jwt;
216                        
217                        return verify(signedJWT);
218
219                } else if (jwt instanceof EncryptedJWT) {
220                
221                        EncryptedJWT encryptedJWT = (EncryptedJWT)jwt;
222                        
223                        return decrypt(encryptedJWT);
224                        
225                } else {
226                
227                        throw new JOSEException("Unexpected JWT type: " + jwt.getClass());
228                }
229        }
230}