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