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.JWTClaimsSet; 019import com.nimbusds.jwt.PlainJWT; 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.supportedJWSAlgorithms()) { 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.supportedJWEAlgorithms()) { 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 JWTClaimsSet 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 JWTClaimsSet 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 JWTClaimsSet 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}