Updated validator structure and id token checking.

pull/210/head
Justin Richer 2012-08-17 16:18:08 -04:00
parent a0df7ad04b
commit 26d5a846e0
4 changed files with 82 additions and 181 deletions

View File

@ -19,33 +19,25 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.mitre.jwt.model.JwtClaims;
import org.mitre.jwt.signer.JwsAlgorithm;
import org.mitre.jwt.signer.JwtSigner;
import org.mitre.jwt.signer.impl.RsaSigner;
@ -64,7 +56,6 @@ import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.WebUtils;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@ -212,9 +203,7 @@ public class AbstractOIDCAuthenticationFilter extends
* javax.servlet.http.HttpServletResponse)
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException,
IOException, ServletException {
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
logger.debug("Request: "
+ request.getRequestURI()
@ -237,9 +226,7 @@ public class AbstractOIDCAuthenticationFilter extends
* @throws Exception
* @throws UnsupportedEncodingException
*/
protected Authentication handleAuthorizationGrantResponse(
String authorizationCode, HttpServletRequest request,
OIDCServerConfiguration serverConfig) {
protected Authentication handleAuthorizationGrantResponse(String authorizationCode, HttpServletRequest request, OIDCServerConfiguration serverConfig) {
final boolean debug = logger.isDebugEnabled();
@ -356,58 +343,59 @@ public class AbstractOIDCAuthenticationFilter extends
refreshTokenValue = tokenResponse.get("refresh_token").getAsString();
}
JwtSigningAndValidationService jwtValidator = getValidatorForServer(serverConfig);
IdToken idToken = null;
try {
idToken = IdToken.parse(idTokenValue);
} catch (AuthenticationServiceException e) {
// I suspect this could happen
logger.error("Problem parsing id_token: " + e);
// e.printStackTrace();
throw new AuthenticationServiceException("Problem parsing id_token return from Token endpoint: " + e);
}
IdToken idToken = IdToken.parse(idTokenValue); // TODO: catch parsing errors?
// validate our ID Token over a number of tests
JwtClaims idClaims = idToken.getClaims();
if(!jwtValidator.validateSignature(idToken.toString())) {
throw new AuthenticationServiceException("Signature not validated");
// check the signature
JwtSigningAndValidationService jwtValidator = getValidatorForServer(serverConfig);
if (jwtValidator != null) {
if(!jwtValidator.validateSignature(idToken.toString())) {
throw new AuthenticationServiceException("Signature validation failed");
}
} else {
logger.info("No validation service found. Skipping signature validation");
}
if(idToken.getClaims().getIssuer() == null) {
throw new AuthenticationServiceException("Issuer is null");
// check the issuer
if (idClaims.getIssuer() == null) {
throw new AuthenticationServiceException("Id Token Issuer is null");
} else if (!idClaims.getIssuer().equals(serverConfig.getIssuer())){
throw new AuthenticationServiceException("Issuers do not match, expected " + serverConfig.getIssuer() + " got " + idClaims.getIssuer());
}
if(!idToken.getClaims().getIssuer().equals(serverConfig.getIssuer())){
throw new AuthenticationServiceException("Issuers do not match");
// check expiration
if (idClaims.getExpiration() == null) {
throw new AuthenticationServiceException("Id Token does not have required expiration claim");
} else {
// it's not null, see if it's expired
Date now = new Date();
if (!now.after(idClaims.getExpiration())) {
throw new AuthenticationServiceException("Id Token is expired: " + idClaims.getExpiration());
}
}
if(jwtValidator.isJwtExpired(idToken)) {
throw new AuthenticationServiceException("Id Token is expired");
// check audience
if (idClaims.getAudience() == null) {
throw new AuthenticationServiceException("Id token audience is null");
} else if (!idClaims.getAudience().equals(serverConfig.getClientId())) {
throw new AuthenticationServiceException("Audience does not match, expected " + serverConfig.getClientId() + " got " + idClaims.getAudience());
}
if(!jwtValidator.validateIssuedAt(idToken)) {
throw new AuthenticationServiceException("Id Token issuedAt failed");
// check issued at
if (idClaims.getIssuedAt() == null) {
throw new AuthenticationServiceException("Id Token does not have required issued-at claim");
} else {
// since it's not null, see if it was issued in the future
Date now = new Date();
if (now.before(idClaims.getIssuedAt())) {
throw new AuthenticationServiceException("Id Token was issued in the future: " + idClaims.getIssuedAt());
}
}
// Clients are required to compare nonce claim in ID token to
// the nonce sent in the Authorization request. The client
// stores this value as a signed session cookie to detect a
// replay by third parties.
//
// See: OpenID Connect Messages Section 2.1.1 entitled "ID Token"
//
// http://openid.net/specs/openid-connect-messages-1_0.html#id_token
//
//String nonce = idToken.getClaims().getClaimAsString("nonce");
String nonce = idToken.getClaims().getNonce();
// compare the nonce to our stored claim
String nonce = idClaims.getNonce();
if (StringUtils.isBlank(nonce)) {
logger.error("ID token did not contain a nonce claim.");
@ -429,10 +417,9 @@ public class AbstractOIDCAuthenticationFilter extends
String userId = idToken.getTokenClaims().getUserId();
// construct an OpenIdConnectAuthenticationToken and return
// a Authentication object w/the userId and the idToken
// construct an OpenIdConnectAuthenticationToken and return a Authentication object w/the userId and the idToken
OpenIdConnectAuthenticationToken token = new OpenIdConnectAuthenticationToken(userId, idToken.getClaims().getIssuer(), serverConfig, idTokenValue, accessTokenValue, refreshTokenValue);
OpenIdConnectAuthenticationToken token = new OpenIdConnectAuthenticationToken(userId, idClaims.getIssuer(), serverConfig, idTokenValue, accessTokenValue, refreshTokenValue);
Authentication authentication = this.getAuthenticationManager().authenticate(token);
@ -615,13 +602,15 @@ public class AbstractOIDCAuthenticationFilter extends
KeyFetcher keyFetch = new KeyFetcher();
PublicKey signingKey = null;
// If the config has the X509 url, prefer that to the JWK
if(serverConfig.getX509SigningUrl() != null) {
signingKey = keyFetch.retrieveX509Key(serverConfig);
} else {
// otherwise prefer the JWK
if (serverConfig.getJwkSigningUrl() != null) {
// prefer the JWK
signingKey = keyFetch.retrieveJwkKey(serverConfig);
} else if (serverConfig.getX509SigningUrl() != null) {
// use the x509 only if JWK isn't configured
signingKey = keyFetch.retrieveX509Key(serverConfig);
} else {
// no keys configured
logger.warn("No server key URLs configured for " + serverConfig.getIssuer());
}
if (signingKey != null) {
@ -648,6 +637,7 @@ public class AbstractOIDCAuthenticationFilter extends
return signingAndValidationService;
} else {
// there were either no keys returned or no URLs configured to fetch them, assume no checking on key signatures
return null;
}
}

View File

@ -30,17 +30,6 @@ public interface JwtSigningAndValidationService {
*/
public Map<String, JwtSigner> getAllSigners();
/**
* Check to see if this JWT has expired or not
*
* @param jwt
* the JWT to check
* @return true if this JWT has an expiration and it has passed, false if
* the JWT has no expiration or it has an expiration and the
* expiration has not passed
*/
public boolean isJwtExpired(Jwt jwt);
/**
* Checks the signature of the given JWT against all configured signers,
* returns true if at least one of the signers validates it.
@ -53,36 +42,6 @@ public interface JwtSigningAndValidationService {
public boolean validateSignature(String jwtString);
/**
* Checks to see when this JWT was issued
*
* @param jwt
* the JWT to check
* @return true if the issued at is valid, false if not
* @throws NoSuchAlgorithmException
*/
public boolean validateIssuedAt(Jwt jwt);
/**
* Checks to see if the nonce parameter sent in the Authorization Request
* is equal to the nonce parameter in the id token
*
* @param jwt
* @param nonce
* the string representation of the Nonce
* @return true if both nonce parameters are equal, false if otherwise
* @throws NoSuchAlgorithmException
*/
public boolean validateNonce(Jwt jwt, String nonce);
/**
* Sign a jwt using the selected algorithm. The algorithm is selected using the String parameter values specified
* in the JWT spec, section 6. I.E., "HS256" means HMAC with SHA-256 and corresponds to our HmacSigner class.
*
* @param jwt the jwt to sign
* @param alg the name of the algorithm to use, as specified in JWS s.6
* @return the signed jwt
*/
/**
* Called to sign a jwt in place for a client that hasn't registered a preferred signing algorithm.
@ -94,6 +53,14 @@ public interface JwtSigningAndValidationService {
*/
public void signJwt(Jwt jwt) throws NoSuchAlgorithmException;
/**
* Sign a jwt using the selected algorithm. The algorithm is selected using the String parameter values specified
* in the JWT spec, section 6. I.E., "HS256" means HMAC with SHA-256 and corresponds to our HmacSigner class.
*
* @param jwt the jwt to sign
* @param alg the name of the algorithm to use, as specified in JWS s.6
* @return the signed jwt
*/
//TODO: implement later; only need signJwt(Jwt jwt) for now
//public Jwt signJwt(Jwt jwt, String alg);

View File

@ -1,69 +0,0 @@
package org.mitre.jwt.signer.service.impl;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.Map;
import org.mitre.jwt.model.Jwt;
import org.mitre.jwt.signer.JwtSigner;
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
public abstract class AbstractJwtSigningAndValidationService implements JwtSigningAndValidationService{
/**
* Return the JwtSigners associated with this service
*
* @return
*/
public abstract Map<String, ? extends JwtSigner> getSigners();
@Override
public boolean isJwtExpired(Jwt jwt) {
Date expiration = jwt.getClaims().getExpiration();
if (expiration != null) {
return new Date().after(expiration);
} else {
return false;
}
}
@Override
public boolean validateSignature(String jwtString) {
for (JwtSigner signer : getSigners().values()) {
try {
if (signer.verify(jwtString)) {
return true;
}
} catch (NoSuchAlgorithmException e) {
// ignore, signer didn't verify signature, try the next one
e.printStackTrace();
}
}
return false;
}
@Override
public boolean validateIssuedAt(Jwt jwt) {
Date issuedAt = jwt.getClaims().getIssuedAt();
if (issuedAt != null) {
// make sure the token was issued in the past
return new Date().after(issuedAt);
} else {
return false;
}
}
@Override
public boolean validateNonce(Jwt jwt, String nonce) {
if(jwt.getClaims().getNonce().equals(nonce)){
return true;
} else {
return false;
}
}
}

View File

@ -28,8 +28,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
public class JwtSigningAndValidationServiceDefault extends AbstractJwtSigningAndValidationService implements
JwtSigningAndValidationService, InitializingBean {
public class JwtSigningAndValidationServiceDefault implements JwtSigningAndValidationService, InitializingBean {
@Autowired
private ConfigurationPropertiesBean configBean;
@ -51,8 +50,7 @@ public class JwtSigningAndValidationServiceDefault extends AbstractJwtSigningAnd
* @param signer
* List of JwtSigners to associate with this service
*/
public JwtSigningAndValidationServiceDefault(
Map<String, ? extends JwtSigner> signer) {
public JwtSigningAndValidationServiceDefault(Map<String, ? extends JwtSigner> signer) {
setSigners(signer);
}
@ -106,8 +104,7 @@ public class JwtSigningAndValidationServiceDefault extends AbstractJwtSigningAnd
*/
@Override
public String toString() {
return "JwtSigningAndValidationServiceDefault [signers=" + signers
+ "]";
return "JwtSigningAndValidationServiceDefault [signers=" + signers + "]";
}
/**
@ -150,4 +147,20 @@ public class JwtSigningAndValidationServiceDefault extends AbstractJwtSigningAnd
return signers;
}
@Override
public boolean validateSignature(String jwtString) {
for (JwtSigner signer : getSigners().values()) {
try {
if (signer.verify(jwtString)) {
return true;
}
} catch (NoSuchAlgorithmException e) {
// ignore, signer didn't verify signature, try the next one
e.printStackTrace();
}
}
return false;
}
}