001package com.nimbusds.jwt.proc; 002 003 004import java.security.Key; 005import java.text.ParseException; 006import java.util.List; 007import java.util.ListIterator; 008 009import com.nimbusds.jose.JOSEException; 010import com.nimbusds.jose.JWEDecrypter; 011import com.nimbusds.jose.JWSVerifier; 012import com.nimbusds.jose.proc.*; 013import com.nimbusds.jwt.*; 014 015 016/** 017 * Default processor of received {@link com.nimbusds.jwt.JWT JSON Web Token}s. 018 * 019 * <p>Must be supplied with a {@link JWSKeySelector JWS key selector} to 020 * determine the key candidate(s) for the signature verification. The exact key 021 * selection procedure is application-specific and may involve key ID lookup, a 022 * certificate check and / or other information supplied in the message 023 * {@link SecurityContext context}. 024 * 025 * <p>Similarly, the processor must be supplied with a {@link JWEKeySelector 026 * JWE key selector} if JWE messages are expected to be processed. 027 * 028 * <p>See sections 6 of RFC 7515 (JWS) and RFC 7516 (JWE) for guidelines on key 029 * selection. 030 * 031 * <p>This processor comes with the default {@link DefaultJWSVerifierFactory 032 * JWS verifier factory} and the default {@link DefaultJWEDecrypterFactory 033 * JWE decrypter factory}; they can construct verifiers / decrypters for all 034 * standard JOSE algorithms implemented by the library. 035 * 036 * <p>Note that for security reasons this processor is hardwired to reject 037 * unsecured (plain) JWTs. Override the {@link #process(PlainJWT, SecurityContext)} 038 * if you need to handle plain JWTs as well. 039 * 040 * <p>An optional {@link JWTClaimsVerifier JWT claims verifier} may be set to 041 * perform various application-specific JWT claims checks, such as issuer 042 * acceptance, after successful JWS verification / JWE decryption. 043 * 044 * <p>To process generic JOSE objects (with arbitrary payloads) use the 045 * {@link com.nimbusds.jose.proc.DefaultJOSEProcessor} class. 046 * 047 * @author Vladimir Dzhuvinov 048 * @version 2015-07-01 049 */ 050public class DefaultJWTProcessor<C extends SecurityContext> 051 extends BaseJOSEProcessor<C> 052 implements JWTProcessor<ReadOnlyJWTClaimsSet, C> { 053 054 055 /** 056 * Optional claims verifier. 057 */ 058 private JWTClaimsVerifier claimsVerifier; 059 060 061 /** 062 * Gets the optional JWT claims verifier. Intended to perform various 063 * application-specific JWT claims checks, such as issuer acceptance, 064 * after successful JWS verification / JWE decryption. 065 * 066 * @return The JWT claims verifier, {@code null} if not specified. 067 */ 068 public JWTClaimsVerifier getJWTClaimsVerifier() { 069 070 return claimsVerifier; 071 } 072 073 074 /** 075 * Sets the optional JWT claims verifier. Intended to perform various 076 * application-specific JWT claims checks, such as issuer acceptance, 077 * after successful JWS verification / JWE decryption. 078 * 079 * @param claimsVerifier The JWT claims verifier, {@code null} if not 080 * specified. 081 */ 082 public void setJWTClaimsVerifier(final JWTClaimsVerifier claimsVerifier) { 083 084 this.claimsVerifier = claimsVerifier; 085 } 086 087 088 /** 089 * Verifies the claims of the specified JWT. 090 * 091 * @param jwt The JWT. Must be in a state which allows the claims to 092 * be extracted. 093 * 094 * @return The JWT claims set. 095 * 096 * @throws BadJWTException If the JWT claims are invalid or rejected. 097 */ 098 private ReadOnlyJWTClaimsSet verifyAndReturnClaims(final JWT jwt) 099 throws BadJWTException { 100 101 ReadOnlyJWTClaimsSet claimsSet; 102 103 try { 104 claimsSet = jwt.getJWTClaimsSet(); 105 106 } catch (ParseException e) { 107 // Payload not a JSON object 108 throw new BadJWTException(e.getMessage(), e); 109 } 110 111 if (claimsVerifier != null) { 112 claimsVerifier.verify(claimsSet); 113 } 114 115 return claimsSet; 116 } 117 118 119 @Override 120 public ReadOnlyJWTClaimsSet process(final String jwtString, final C context) 121 throws ParseException, BadJOSEException, JOSEException { 122 123 return process(JWTParser.parse(jwtString), context); 124 } 125 126 127 @Override 128 public ReadOnlyJWTClaimsSet process(final JWT jwt, final C context) 129 throws BadJOSEException, JOSEException { 130 131 if (jwt instanceof SignedJWT) { 132 return process((SignedJWT)jwt, context); 133 } 134 135 if (jwt instanceof EncryptedJWT) { 136 return process((EncryptedJWT)jwt, context); 137 } 138 139 if (jwt instanceof PlainJWT) { 140 return process((PlainJWT)jwt, context); 141 } 142 143 // Should never happen 144 throw new JOSEException("Unexpected JWT object type: " + jwt.getClass()); 145 } 146 147 148 @Override 149 public ReadOnlyJWTClaimsSet process(final PlainJWT plainJWT, final C context) 150 throws BadJOSEException, JOSEException { 151 152 verifyAndReturnClaims(plainJWT); // just check claims, no return 153 154 throw new BadJOSEException("Unsecured (plain) JWTs are rejected, extend class to handle"); 155 } 156 157 158 @Override 159 public ReadOnlyJWTClaimsSet process(final SignedJWT signedJWT, final C context) 160 throws BadJOSEException, JOSEException { 161 162 if (getJWSKeySelector() == null) { 163 // JWS key selector may have been deliberately omitted 164 throw new BadJOSEException("Signed JWT rejected: No JWS key selector is configured"); 165 } 166 167 if (getJWSVerifierFactory() == null) { 168 throw new JOSEException("No JWS verifier is configured"); 169 } 170 171 List<? extends Key> keyCandidates = getJWSKeySelector().selectJWSKeys(signedJWT.getHeader(), context); 172 173 if (keyCandidates == null || keyCandidates.isEmpty()) { 174 throw new BadJOSEException("Signed JWT rejected: No matching key(s) found"); 175 } 176 177 ListIterator<? extends Key> it = keyCandidates.listIterator(); 178 179 while (it.hasNext()) { 180 181 JWSVerifier verifier = getJWSVerifierFactory().createJWSVerifier(signedJWT.getHeader(), it.next()); 182 183 if (verifier == null) { 184 continue; 185 } 186 187 final boolean validSignature = signedJWT.verify(verifier); 188 189 if (validSignature) { 190 return verifyAndReturnClaims(signedJWT); 191 } 192 193 if (! it.hasNext()) { 194 // No more keys to try out 195 throw new BadJWSException("Signed JWT rejected: Invalid signature"); 196 } 197 } 198 199 throw new BadJOSEException("JWS object rejected: No matching verifier(s) found"); 200 } 201 202 203 @Override 204 public ReadOnlyJWTClaimsSet process(final EncryptedJWT encryptedJWT, final C context) 205 throws BadJOSEException, JOSEException { 206 207 if (getJWEKeySelector() == null) { 208 // JWE key selector may have been deliberately omitted 209 throw new BadJOSEException("Encrypted JWT rejected: No JWE key selector is configured"); 210 } 211 212 if (getJWEDecrypterFactory() == null) { 213 throw new JOSEException("No JWE decrypter is configured"); 214 } 215 216 List<? extends Key> keyCandidates = getJWEKeySelector().selectJWEKeys(encryptedJWT.getHeader(), context); 217 218 if (keyCandidates == null || keyCandidates.isEmpty()) { 219 throw new BadJOSEException("Encrypted JWT rejected: No matching key(s) found"); 220 } 221 222 ListIterator<? extends Key> it = keyCandidates.listIterator(); 223 224 while (it.hasNext()) { 225 226 JWEDecrypter decrypter = getJWEDecrypterFactory().createJWEDecrypter(encryptedJWT.getHeader(), it.next()); 227 228 if (decrypter == null) { 229 continue; 230 } 231 232 try { 233 encryptedJWT.decrypt(decrypter); 234 235 } catch (JOSEException e) { 236 237 if (it.hasNext()) { 238 // Try next key 239 continue; 240 } 241 242 // No more keys to try 243 throw new BadJWEException("Encrypted JWT rejected: " + e.getMessage(), e); 244 } 245 246 if ("JWT".equalsIgnoreCase(encryptedJWT.getHeader().getContentType())) { 247 248 // Handle nested signed JWT, see http://tools.ietf.org/html/rfc7519#section-5.2 249 SignedJWT nestedJWT = encryptedJWT.getPayload().toSignedJWT(); 250 251 if (nestedJWT == null) { 252 // Cannot parse payload to signed JWT 253 throw new BadJWTException("The payload is not a nested JWT"); 254 } 255 256 return process(nestedJWT, context); 257 } 258 259 return verifyAndReturnClaims(encryptedJWT); 260 } 261 262 throw new BadJOSEException("Encrypted JWT rejected: No matching decrypter(s) found"); 263 } 264}