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}