KEYCLOAK-2106 HTTP 500 for unparsable refresh tokens

This commit is contained in:
Stian Thorgersen 2015-11-26 21:15:22 +01:00
parent 5ea880cfff
commit c83e3bd2d1
20 changed files with 201 additions and 149 deletions

View file

@ -5,6 +5,7 @@ import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.constants.AdapterConstants; import org.keycloak.constants.AdapterConstants;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
@ -51,7 +52,13 @@ public class KeycloakOIDCIdentityProvider extends OIDCIdentityProvider {
@POST @POST
@Path(AdapterConstants.K_LOGOUT) @Path(AdapterConstants.K_LOGOUT)
public Response backchannelLogout(String input) { public Response backchannelLogout(String input) {
JWSInput token = new JWSInput(input); JWSInput token = null;
try {
token = new JWSInput(input);
} catch (JWSInputException e) {
logger.warn("Failed to verify logout request");
return Response.status(400).build();
}
PublicKey key = getExternalIdpKey(); PublicKey key = getExternalIdpKey();
if (key != null) { if (key != null) {
if (!verify(token, key)) { if (!verify(token, key)) {

View file

@ -29,6 +29,7 @@ import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.RSAProvider; import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
@ -282,40 +283,41 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
throw new IdentityBrokerException("No token from server."); throw new IdentityBrokerException("No token from server.");
} }
JsonWebToken token;
try { try {
JWSInput jws = new JWSInput(encodedToken); JWSInput jws = new JWSInput(encodedToken);
if (!verify(jws, key)) { if (!verify(jws, key)) {
throw new IdentityBrokerException("token signature validation failed"); throw new IdentityBrokerException("token signature validation failed");
} }
JsonWebToken token = jws.readJsonContent(JsonWebToken.class); token = jws.readJsonContent(JsonWebToken.class);
} catch (JWSInputException e) {
String iss = token.getIssuer(); throw new IdentityBrokerException("Invalid token", e);
if (!token.hasAudience(getConfig().getClientId())) {
throw new IdentityBrokerException("Wrong audience from token.");
}
if (!token.isActive()) {
throw new IdentityBrokerException("Token is no longer valid");
}
String trustedIssuers = getConfig().getIssuer();
if (trustedIssuers != null) {
String[] issuers = trustedIssuers.split(",");
for (String trustedIssuer : issuers) {
if (iss != null && iss.equals(trustedIssuer.trim())) {
return token;
}
}
throw new IdentityBrokerException("Wrong issuer from token. Got: " + iss + " expected: " + getConfig().getIssuer());
}
return token;
} catch (IOException e) {
throw new IdentityBrokerException("Could not decode token.", e);
} }
String iss = token.getIssuer();
if (!token.hasAudience(getConfig().getClientId())) {
throw new IdentityBrokerException("Wrong audience from token.");
}
if (!token.isActive()) {
throw new IdentityBrokerException("Token is no longer valid");
}
String trustedIssuers = getConfig().getIssuer();
if (trustedIssuers != null) {
String[] issuers = trustedIssuers.split(",");
for (String trustedIssuer : issuers) {
if (iss != null && iss.equals(trustedIssuer.trim())) {
return token;
}
}
throw new IdentityBrokerException("Wrong issuer from token. Got: " + iss + " expected: " + getConfig().getIssuer());
}
return token;
} }
@Override @Override

View file

@ -2,6 +2,7 @@ package org.keycloak;
import org.keycloak.common.VerificationException; import org.keycloak.common.VerificationException;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.RSAProvider; import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.util.TokenUtil; import org.keycloak.util.TokenUtil;
@ -22,7 +23,7 @@ public class RSATokenVerifier {
JWSInput input = null; JWSInput input = null;
try { try {
input = new JWSInput(tokenString); input = new JWSInput(tokenString);
} catch (Exception e) { } catch (JWSInputException e) {
throw new VerificationException("Couldn't parse token", e); throw new VerificationException("Couldn't parse token", e);
} }
if (!isPublicKeyValid(input, realmKey)) throw new VerificationException("Invalid token signature."); if (!isPublicKeyValid(input, realmKey)) throw new VerificationException("Invalid token signature.");
@ -30,7 +31,7 @@ public class RSATokenVerifier {
AccessToken token; AccessToken token;
try { try {
token = input.readJsonContent(AccessToken.class); token = input.readJsonContent(AccessToken.class);
} catch (IOException e) { } catch (JWSInputException e) {
throw new VerificationException("Couldn't parse token signature", e); throw new VerificationException("Couldn't parse token signature", e);
} }
String user = token.getSubject(); String user = token.getSubject();

View file

@ -21,14 +21,14 @@ public class JWSInput {
byte[] signature; byte[] signature;
public JWSInput(String wire) { public JWSInput(String wire) throws JWSInputException {
this.wireString = wire;
String[] parts = wire.split("\\.");
if (parts.length < 2 || parts.length > 3) throw new IllegalArgumentException("Parsing error");
encodedHeader = parts[0];
encodedContent = parts[1];
encodedSignatureInput = encodedHeader + '.' + encodedContent;
try { try {
this.wireString = wire;
String[] parts = wire.split("\\.");
if (parts.length < 2 || parts.length > 3) throw new IllegalArgumentException("Parsing error");
encodedHeader = parts[0];
encodedContent = parts[1];
encodedSignatureInput = encodedHeader + '.' + encodedContent;
content = Base64Url.decode(encodedContent); content = Base64Url.decode(encodedContent);
if (parts.length > 2) { if (parts.length > 2) {
encodedSignature = parts[2]; encodedSignature = parts[2];
@ -37,8 +37,8 @@ public class JWSInput {
} }
byte[] headerBytes = Base64Url.decode(encodedHeader); byte[] headerBytes = Base64Url.decode(encodedHeader);
header = JsonSerialization.readValue(headerBytes, JWSHeader.class); header = JsonSerialization.readValue(headerBytes, JWSHeader.class);
} catch (Exception e) { } catch (Throwable t) {
throw new RuntimeException(e); throw new JWSInputException(t);
} }
} }
@ -80,8 +80,12 @@ public class JWSInput {
return header.getAlgorithm().getProvider().verify(this, key); return header.getAlgorithm().getProvider().verify(this, key);
} }
public <T> T readJsonContent(Class<T> type) throws IOException { public <T> T readJsonContent(Class<T> type) throws JWSInputException {
return JsonSerialization.readValue(content, type); try {
return JsonSerialization.readValue(content, type);
} catch (IOException e) {
throw new JWSInputException(e);
}
} }
public String readContentAsString() { public String readContentAsString() {

View file

@ -0,0 +1,18 @@
package org.keycloak.jose.jws;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JWSInputException extends Exception {
public JWSInputException(String s) {
super(s);
}
public JWSInputException() {
}
public JWSInputException(Throwable throwable) {
super(throwable);
}
}

View file

@ -64,7 +64,7 @@ public class RSAProvider implements SignatureProvider {
verifier.update(input.getEncodedSignatureInput().getBytes("UTF-8")); verifier.update(input.getEncodedSignatureInput().getBytes("UTF-8"));
return verifier.verify(input.getSignature()); return verifier.verify(input.getSignature());
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); return false;
} }
} }

View file

@ -4,6 +4,7 @@ import java.io.IOException;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.RefreshToken; import org.keycloak.representations.RefreshToken;
/** /**
@ -41,11 +42,15 @@ public class TokenUtil {
* @param decodedToken * @param decodedToken
* @return * @return
*/ */
public static RefreshToken getRefreshToken(byte[] decodedToken) throws IOException { public static RefreshToken getRefreshToken(byte[] decodedToken) throws JWSInputException {
return JsonSerialization.readValue(decodedToken, RefreshToken.class); try {
return JsonSerialization.readValue(decodedToken, RefreshToken.class);
} catch (IOException e) {
throw new JWSInputException(e);
}
} }
public static RefreshToken getRefreshToken(String refreshToken) throws IOException { public static RefreshToken getRefreshToken(String refreshToken) throws JWSInputException {
byte[] encodedContent = new JWSInput(refreshToken).getContent(); byte[] encodedContent = new JWSInput(refreshToken).getContent();
return getRefreshToken(encodedContent); return getRefreshToken(encodedContent);
} }
@ -56,13 +61,9 @@ public class TokenUtil {
* @param refreshToken * @param refreshToken
* @return * @return
*/ */
public static boolean isOfflineToken(String refreshToken) { public static boolean isOfflineToken(String refreshToken) throws JWSInputException {
try { RefreshToken token = getRefreshToken(refreshToken);
RefreshToken token = getRefreshToken(refreshToken); return token.getType().equals(TOKEN_TYPE_OFFLINE);
return token.getType().equals(TOKEN_TYPE_OFFLINE);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
} }
} }

View file

@ -23,6 +23,7 @@ import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext; import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.ServerRequest; import org.keycloak.adapters.ServerRequest;
import org.keycloak.adapters.spi.LogoutError; import org.keycloak.adapters.spi.LogoutError;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.RefreshToken; import org.keycloak.representations.RefreshToken;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
@ -49,40 +50,44 @@ public class OfflineAccessPortalServlet extends HttpServlet {
@Override @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
if (req.getRequestURI().endsWith("/login")) {
storeToken(req);
req.getRequestDispatcher("/WEB-INF/pages/loginCallback.jsp").forward(req, resp);
return;
}
if (req.getRequestURI().endsWith("/login")) { String refreshToken = RefreshTokenDAO.loadToken();
storeToken(req); String refreshTokenInfo;
req.getRequestDispatcher("/WEB-INF/pages/loginCallback.jsp").forward(req, resp); boolean savedTokenAvailable;
return; if (refreshToken == null) {
refreshTokenInfo = "No token saved in database. Please login first";
savedTokenAvailable = false;
} else {
RefreshToken refreshTokenDecoded = null;
refreshTokenDecoded = TokenUtil.getRefreshToken(refreshToken);
String exp = (refreshTokenDecoded.getExpiration() == 0) ? "NEVER" : Time.toDate(refreshTokenDecoded.getExpiration()).toString();
refreshTokenInfo = String.format("<p>Type: %s</p><p>ID: %s</p><p>Expires: %s</p>", refreshTokenDecoded.getType(), refreshTokenDecoded.getId(), exp);
savedTokenAvailable = true;
}
req.setAttribute("tokenInfo", refreshTokenInfo);
req.setAttribute("savedTokenAvailable", savedTokenAvailable);
String customers;
if (req.getRequestURI().endsWith("/loadCustomers")) {
customers = loadCustomers(req, refreshToken);
} else {
customers = "";
}
req.setAttribute("customers", customers);
req.getRequestDispatcher("/WEB-INF/pages/view.jsp").forward(req, resp);
} catch (JWSInputException e) {
throw new ServletException(e);
} }
String refreshToken = RefreshTokenDAO.loadToken();
String refreshTokenInfo;
boolean savedTokenAvailable;
if (refreshToken == null) {
refreshTokenInfo = "No token saved in database. Please login first";
savedTokenAvailable = false;
} else {
RefreshToken refreshTokenDecoded = TokenUtil.getRefreshToken(refreshToken);
String exp = (refreshTokenDecoded.getExpiration() == 0) ? "NEVER" : Time.toDate(refreshTokenDecoded.getExpiration()).toString();
refreshTokenInfo = String.format("<p>Type: %s</p><p>ID: %s</p><p>Expires: %s</p>", refreshTokenDecoded.getType(), refreshTokenDecoded.getId(), exp);
savedTokenAvailable = true;
}
req.setAttribute("tokenInfo", refreshTokenInfo);
req.setAttribute("savedTokenAvailable", savedTokenAvailable);
String customers;
if (req.getRequestURI().endsWith("/loadCustomers")) {
customers = loadCustomers(req, refreshToken);
} else {
customers = "";
}
req.setAttribute("customers", customers);
req.getRequestDispatcher("/WEB-INF/pages/view.jsp").forward(req, resp);
} }
private void storeToken(HttpServletRequest req) throws IOException { private void storeToken(HttpServletRequest req) throws IOException, JWSInputException {
RefreshableKeycloakSecurityContext ctx = (RefreshableKeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName()); RefreshableKeycloakSecurityContext ctx = (RefreshableKeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName());
String refreshToken = ctx.getRefreshToken(); String refreshToken = ctx.getRefreshToken();

View file

@ -9,6 +9,7 @@ import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.VerificationException; import org.keycloak.common.VerificationException;
import org.keycloak.constants.AdapterConstants; import org.keycloak.constants.AdapterConstants;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.common.util.KeycloakUriBuilder;
@ -58,10 +59,10 @@ public class CookieTokenStore {
AccessToken accessToken = RSATokenVerifier.verifyToken(accessTokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl(), false, true); AccessToken accessToken = RSATokenVerifier.verifyToken(accessTokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl(), false, true);
IDToken idToken; IDToken idToken;
if (idTokenString != null && idTokenString.length() > 0) { if (idTokenString != null && idTokenString.length() > 0) {
JWSInput input = new JWSInput(idTokenString);
try { try {
JWSInput input = new JWSInput(idTokenString);
idToken = input.readJsonContent(IDToken.class); idToken = input.readJsonContent(IDToken.class);
} catch (IOException e) { } catch (JWSInputException e) {
throw new VerificationException(e); throw new VerificationException(e);
} }
} else { } else {

View file

@ -11,6 +11,7 @@ import org.keycloak.common.VerificationException;
import org.keycloak.constants.AdapterConstants; import org.keycloak.constants.AdapterConstants;
import org.keycloak.enums.TokenStore; import org.keycloak.enums.TokenStore;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
@ -313,10 +314,10 @@ public class OAuthRequestAuthenticator {
try { try {
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl()); token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
if (idTokenString != null) { if (idTokenString != null) {
JWSInput input = new JWSInput(idTokenString);
try { try {
JWSInput input = new JWSInput(idTokenString);
idToken = input.readJsonContent(IDToken.class); idToken = input.readJsonContent(IDToken.class);
} catch (IOException e) { } catch (JWSInputException e) {
throw new VerificationException(); throw new VerificationException();
} }
} }

View file

@ -3,6 +3,7 @@ package org.keycloak.adapters;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.spi.UserSessionManagement; import org.keycloak.adapters.spi.UserSessionManagement;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.VersionRepresentation; import org.keycloak.representations.VersionRepresentation;
import org.keycloak.constants.AdapterConstants; import org.keycloak.constants.AdapterConstants;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
@ -178,18 +179,17 @@ public class PreAuthActionsHandler {
return null; return null;
} }
JWSInput input = new JWSInput(token);
boolean verified = false;
try { try {
verified = RSAProvider.verify(input, deployment.getRealmKey()); JWSInput input = new JWSInput(token);
} catch (Exception ignore) { if (RSAProvider.verify(input, deployment.getRealmKey())) {
return input;
}
} catch (JWSInputException ignore) {
} }
if (!verified) {
log.warn("admin request failed, unable to verify token"); log.warn("admin request failed, unable to verify token");
facade.getResponse().sendError(403, "no token"); facade.getResponse().sendError(403, "no token");
return null; return null;
}
return input;
} }

View file

@ -8,6 +8,7 @@ import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder; import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.ServerRequest; import org.keycloak.adapters.ServerRequest;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
@ -195,10 +196,10 @@ public class KeycloakInstalled {
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl()); token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
if (idTokenString != null) { if (idTokenString != null) {
JWSInput input = new JWSInput(idTokenString);
try { try {
JWSInput input = new JWSInput(idTokenString);
idToken = input.readJsonContent(IDToken.class); idToken = input.readJsonContent(IDToken.class);
} catch (IOException e) { } catch (JWSInputException e) {
throw new VerificationException(); throw new VerificationException();
} }
} }

View file

@ -9,6 +9,7 @@ import org.keycloak.adapters.ServerRequest;
import org.keycloak.adapters.spi.AuthenticationError; import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.adapters.spi.LogoutError; import org.keycloak.adapters.spi.LogoutError;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.common.util.KeycloakUriBuilder;
@ -153,10 +154,10 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
public static IDToken extractIdToken(String idToken) { public static IDToken extractIdToken(String idToken) {
if (idToken == null) return null; if (idToken == null) return null;
JWSInput input = new JWSInput(idToken);
try { try {
JWSInput input = new JWSInput(idToken);
return input.readJsonContent(IDToken.class); return input.readJsonContent(IDToken.class);
} catch (IOException e) { } catch (JWSInputException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }

View file

@ -1,6 +1,7 @@
package org.keycloak.models.utils; package org.keycloak.models.utils;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.RSAProvider; import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.OTPPolicy; import org.keycloak.models.OTPPolicy;
import org.keycloak.models.PasswordPolicy; import org.keycloak.models.PasswordPolicy;
@ -73,11 +74,11 @@ public class CredentialValidation {
} }
public static boolean validPasswordToken(RealmModel realm, UserModel user, String encodedPasswordToken) { public static boolean validPasswordToken(RealmModel realm, UserModel user, String encodedPasswordToken) {
JWSInput jws = new JWSInput(encodedPasswordToken);
if (!RSAProvider.verify(jws, realm.getPublicKey())) {
return false;
}
try { try {
JWSInput jws = new JWSInput(encodedPasswordToken);
if (!RSAProvider.verify(jws, realm.getPublicKey())) {
return false;
}
PasswordToken passwordToken = jws.readJsonContent(PasswordToken.class); PasswordToken passwordToken = jws.readJsonContent(PasswordToken.class);
if (!passwordToken.getRealm().equals(realm.getName())) { if (!passwordToken.getRealm().equals(realm.getName())) {
return false; return false;
@ -89,7 +90,7 @@ public class CredentialValidation {
return false; return false;
} }
return true; return true;
} catch (IOException e) { } catch (JWSInputException e) {
return false; return false;
} }
} }

View file

@ -9,6 +9,7 @@ import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.RSAProvider; import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
@ -195,44 +196,46 @@ public class TokenManager {
} }
public RefreshToken verifyRefreshToken(RealmModel realm, String encodedRefreshToken) throws OAuthErrorException { public RefreshToken verifyRefreshToken(RealmModel realm, String encodedRefreshToken) throws OAuthErrorException {
JWSInput jws = new JWSInput(encodedRefreshToken);
RefreshToken refreshToken = null;
try { try {
JWSInput jws = new JWSInput(encodedRefreshToken);
RefreshToken refreshToken = null;
if (!RSAProvider.verify(jws, realm.getPublicKey())) { if (!RSAProvider.verify(jws, realm.getPublicKey())) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token"); throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token");
} }
refreshToken = jws.readJsonContent(RefreshToken.class); refreshToken = jws.readJsonContent(RefreshToken.class);
} catch (Exception e) {
if (refreshToken.getExpiration() != 0 && refreshToken.isExpired()) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Refresh token expired");
}
if (refreshToken.getIssuedAt() < realm.getNotBefore()) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token");
}
return refreshToken;
} catch (JWSInputException e) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", e); throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", e);
} }
if (refreshToken.getExpiration() != 0 && refreshToken.isExpired()) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Refresh token expired");
}
if (refreshToken.getIssuedAt() < realm.getNotBefore()) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token");
}
return refreshToken;
} }
public IDToken verifyIDToken(RealmModel realm, String encodedIDToken) throws OAuthErrorException { public IDToken verifyIDToken(RealmModel realm, String encodedIDToken) throws OAuthErrorException {
JWSInput jws = new JWSInput(encodedIDToken);
IDToken idToken = null;
try { try {
JWSInput jws = new JWSInput(encodedIDToken);
IDToken idToken;
if (!RSAProvider.verify(jws, realm.getPublicKey())) { if (!RSAProvider.verify(jws, realm.getPublicKey())) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid IDToken"); throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid IDToken");
} }
idToken = jws.readJsonContent(IDToken.class); idToken = jws.readJsonContent(IDToken.class);
} catch (IOException e) {
if (idToken.isExpired()) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "IDToken expired");
}
if (idToken.getIssuedAt() < realm.getNotBefore()) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale IDToken");
}
return idToken;
} catch (JWSInputException e) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid IDToken", e); throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid IDToken", e);
} }
if (idToken.isExpired()) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "IDToken expired");
}
if (idToken.getIssuedAt() < realm.getNotBefore()) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale IDToken");
}
return idToken;
} }
public AccessToken createClientAccessToken(KeycloakSession session, Set<RoleModel> requestedRoles, RealmModel realm, ClientModel client, UserModel user, UserSessionModel userSession, ClientSessionModel clientSession) { public AccessToken createClientAccessToken(KeycloakSession session, Set<RoleModel> requestedRoles, RealmModel realm, ClientModel client, UserModel user, UserSessionModel userSession, ClientSessionModel clientSession) {

View file

@ -3,6 +3,7 @@ package org.keycloak.services.clientregistration;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.RSAProvider; import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.ClientInitialAccessModel; import org.keycloak.models.ClientInitialAccessModel;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
@ -44,7 +45,7 @@ public class ClientRegistrationTokenUtils {
JWSInput input; JWSInput input;
try { try {
input = new JWSInput(token); input = new JWSInput(token);
} catch (Exception e) { } catch (JWSInputException e) {
throw new ForbiddenException(e); throw new ForbiddenException(e);
} }
@ -55,7 +56,7 @@ public class ClientRegistrationTokenUtils {
JsonWebToken jwt; JsonWebToken jwt;
try { try {
jwt = input.readJsonContent(JsonWebToken.class); jwt = input.readJsonContent(JsonWebToken.class);
} catch (IOException e) { } catch (JWSInputException e) {
throw new ForbiddenException(e); throw new ForbiddenException(e);
} }

View file

@ -9,6 +9,7 @@ import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.jboss.resteasy.spi.UnauthorizedException; import org.jboss.resteasy.spi.UnauthorizedException;
import org.keycloak.common.ClientConnection; import org.keycloak.common.ClientConnection;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.models.AdminRoles; import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
@ -137,11 +138,11 @@ public class AdminRoot {
protected AdminAuth authenticateRealmAdminRequest(HttpHeaders headers) { protected AdminAuth authenticateRealmAdminRequest(HttpHeaders headers) {
String tokenString = authManager.extractAuthorizationHeaderToken(headers); String tokenString = authManager.extractAuthorizationHeaderToken(headers);
if (tokenString == null) throw new UnauthorizedException("Bearer"); if (tokenString == null) throw new UnauthorizedException("Bearer");
JWSInput input = new JWSInput(tokenString);
AccessToken token; AccessToken token;
try { try {
JWSInput input = new JWSInput(tokenString);
token = input.readJsonContent(AccessToken.class); token = input.readJsonContent(AccessToken.class);
} catch (IOException e) { } catch (JWSInputException e) {
throw new UnauthorizedException("Bearer token format error"); throw new UnauthorizedException("Bearer token format error");
} }
String realmName = token.getIssuer().substring(token.getIssuer().lastIndexOf('/') + 1); String realmName = token.getIssuer().substring(token.getIssuer().lastIndexOf('/') + 1);

View file

@ -38,6 +38,7 @@ import org.keycloak.events.Details;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
import org.keycloak.events.Event; import org.keycloak.events.Event;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.ProtocolMapperModel;
@ -806,26 +807,15 @@ public class AccessTokenTest {
} }
} }
private IDToken getIdToken(org.keycloak.representations.AccessTokenResponse tokenResponse) throws VerificationException { private IDToken getIdToken(org.keycloak.representations.AccessTokenResponse tokenResponse) throws JWSInputException {
JWSInput input = new JWSInput(tokenResponse.getIdToken()); JWSInput input = new JWSInput(tokenResponse.getIdToken());
IDToken idToken = null; return input.readJsonContent(IDToken.class);
try {
idToken = input.readJsonContent(IDToken.class);
} catch (IOException e) {
throw new VerificationException();
}
return idToken;
} }
private AccessToken getAccessToken(org.keycloak.representations.AccessTokenResponse tokenResponse) throws VerificationException { private AccessToken getAccessToken(org.keycloak.representations.AccessTokenResponse tokenResponse) throws JWSInputException {
JWSInput input = new JWSInput(tokenResponse.getToken()); JWSInput input = new JWSInput(tokenResponse.getToken());
AccessToken idToken = null; return input.readJsonContent(AccessToken.class);
try {
idToken = input.readJsonContent(AccessToken.class);
} catch (IOException e) {
throw new VerificationException();
}
return idToken;
} }
protected Response executeGrantAccessTokenRequest(WebTarget grantTarget) { protected Response executeGrantAccessTokenRequest(WebTarget grantTarget) {

View file

@ -25,6 +25,7 @@ import org.keycloak.events.Errors;
import org.keycloak.events.Event; import org.keycloak.events.Event;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
@ -645,7 +646,12 @@ public class OfflineTokenTest {
StringBuilder response = new StringBuilder("<html><head><title>Offline token servlet</title></head><body><pre>"); StringBuilder response = new StringBuilder("<html><head><title>Offline token servlet</title></head><body><pre>");
RefreshableKeycloakSecurityContext ctx = (RefreshableKeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName()); RefreshableKeycloakSecurityContext ctx = (RefreshableKeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName());
String accessTokenPretty = JsonSerialization.writeValueAsPrettyString(ctx.getToken()); String accessTokenPretty = JsonSerialization.writeValueAsPrettyString(ctx.getToken());
RefreshToken refreshToken = new JWSInput(ctx.getRefreshToken()).readJsonContent(RefreshToken.class); RefreshToken refreshToken = null;
try {
refreshToken = new JWSInput(ctx.getRefreshToken()).readJsonContent(RefreshToken.class);
} catch (JWSInputException e) {
throw new IOException(e);
}
String refreshTokenPretty = JsonSerialization.writeValueAsPrettyString(refreshToken); String refreshTokenPretty = JsonSerialization.writeValueAsPrettyString(refreshToken);
response = response.append(accessTokenPretty) response = response.append(accessTokenPretty)

View file

@ -115,6 +115,14 @@ public class RefreshTokenTest {
} }
@Test
public void invalidRefreshToken() throws Exception {
AccessTokenResponse response = oauth.doRefreshTokenRequest("invalid", "password");
Assert.assertEquals(400, response.getStatusCode());
Assert.assertEquals("invalid_grant", response.getError());
events.clear();
}
@Test @Test
public void refreshTokenRequest() throws Exception { public void refreshTokenRequest() throws Exception {
oauth.doLogin("test-user@localhost", "password"); oauth.doLogin("test-user@localhost", "password");