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