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}