Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6826336
commit 33f0100
Showing
21 changed files
with
417 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package org.keycloak.jose; | ||
|
||
import org.junit.Assert; | ||
import org.junit.Test; | ||
import org.keycloak.jose.jws.JWSBuilder; | ||
import org.keycloak.jose.jws.JWSInput; | ||
import org.keycloak.jose.jws.crypto.HMACProvider; | ||
|
||
import javax.crypto.SecretKey; | ||
import javax.crypto.spec.SecretKeySpec; | ||
import java.util.UUID; | ||
|
||
/** | ||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> | ||
* @version $Revision: 1 $ | ||
*/ | ||
public class HmacTest { | ||
|
||
@Test | ||
public void testHmacSignatures() throws Exception { | ||
SecretKey secret = new SecretKeySpec(UUID.randomUUID().toString().getBytes(), "HmacSHA256"); | ||
String encoded = new JWSBuilder().content("hello world".getBytes()) | ||
.hmac256(secret); | ||
JWSInput input = new JWSInput(encoded); | ||
Assert.assertTrue(HMACProvider.verify(input, secret)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
175 changes: 175 additions & 0 deletions
175
services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
package org.keycloak.protocol; | ||
|
||
import org.codehaus.jackson.annotate.JsonProperty; | ||
import org.jboss.logging.Logger; | ||
import org.keycloak.ClientConnection; | ||
import org.keycloak.jose.jws.JWSBuilder; | ||
import org.keycloak.jose.jws.JWSInput; | ||
import org.keycloak.jose.jws.crypto.HMACProvider; | ||
import org.keycloak.jose.jws.crypto.RSAProvider; | ||
import org.keycloak.models.ClientModel; | ||
import org.keycloak.models.ClientSessionModel; | ||
import org.keycloak.models.KeycloakSession; | ||
import org.keycloak.models.RealmModel; | ||
import org.keycloak.services.managers.AuthenticationManager; | ||
import org.keycloak.services.util.CookieHelper; | ||
|
||
import javax.crypto.SecretKey; | ||
import javax.ws.rs.core.Cookie; | ||
import javax.ws.rs.core.UriInfo; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
/** | ||
* This is an an encoded token that is stored as a cookie so that if there is a client timeout, then the client session | ||
* can be restarted. | ||
* | ||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> | ||
* @version $Revision: 1 $ | ||
*/ | ||
public class RestartLoginCookie { | ||
private static final Logger logger = Logger.getLogger(RestartLoginCookie.class); | ||
public static final String KC_RESTART = "KC_RESTART"; | ||
@JsonProperty("cs") | ||
protected String clientSession; | ||
|
||
@JsonProperty("cid") | ||
protected String clientId; | ||
|
||
@JsonProperty("pty") | ||
protected String authMethod; | ||
|
||
@JsonProperty("ruri") | ||
protected String redirectUri; | ||
|
||
@JsonProperty("act") | ||
protected String action; | ||
|
||
@JsonProperty("notes") | ||
protected Map<String, String> notes = new HashMap<>(); | ||
|
||
public String getClientSession() { | ||
return clientSession; | ||
} | ||
|
||
public void setClientSession(String clientSession) { | ||
this.clientSession = clientSession; | ||
} | ||
|
||
public Map<String, String> getNotes() { | ||
return notes; | ||
} | ||
|
||
public void setNotes(Map<String, String> notes) { | ||
this.notes = notes; | ||
} | ||
|
||
public String getClientId() { | ||
return clientId; | ||
} | ||
|
||
public void setClientId(String clientId) { | ||
this.clientId = clientId; | ||
} | ||
|
||
public String getAuthMethod() { | ||
return authMethod; | ||
} | ||
|
||
public void setAuthMethod(String authMethod) { | ||
this.authMethod = authMethod; | ||
} | ||
|
||
public String getRedirectUri() { | ||
return redirectUri; | ||
} | ||
|
||
public void setRedirectUri(String redirectUri) { | ||
this.redirectUri = redirectUri; | ||
} | ||
|
||
public String getAction() { | ||
return action; | ||
} | ||
|
||
public void setAction(String action) { | ||
this.action = action; | ||
} | ||
|
||
public String encode(RealmModel realm) { | ||
JWSBuilder builder = new JWSBuilder(); | ||
return builder.jsonContent(this) | ||
.hmac256((SecretKey)realm.getCodeSecretKey()); | ||
//.rsa256(realm.getPrivateKey()); | ||
|
||
} | ||
|
||
public RestartLoginCookie() { | ||
} | ||
public RestartLoginCookie(ClientSessionModel clientSession) { | ||
this.action = clientSession.getAction(); | ||
this.clientId = clientSession.getClient().getClientId(); | ||
this.authMethod = clientSession.getAuthMethod(); | ||
this.redirectUri = clientSession.getRedirectUri(); | ||
this.clientSession = clientSession.getId(); | ||
for (Map.Entry<String, String> entry : clientSession.getNotes().entrySet()) { | ||
notes.put(entry.getKey(), entry.getValue()); | ||
} | ||
} | ||
|
||
public static void setRestartCookie(RealmModel realm, ClientConnection connection, UriInfo uriInfo, ClientSessionModel clientSession) { | ||
RestartLoginCookie restart = new RestartLoginCookie(clientSession); | ||
String encoded = restart.encode(realm); | ||
int keySize = realm.getCodeSecret().length(); | ||
int size = encoded.length(); | ||
String path = AuthenticationManager.getRealmCookiePath(realm, uriInfo); | ||
boolean secureOnly = realm.getSslRequired().isRequired(connection); | ||
CookieHelper.addCookie(KC_RESTART, encoded, path, null, null, -1, secureOnly, true); | ||
} | ||
|
||
public static void expireRestartCookie(RealmModel realm, ClientConnection connection, UriInfo uriInfo) { | ||
String path = AuthenticationManager.getRealmCookiePath(realm, uriInfo); | ||
boolean secureOnly = realm.getSslRequired().isRequired(connection); | ||
CookieHelper.addCookie(KC_RESTART, "", path, null, null, 0, secureOnly, true); | ||
} | ||
|
||
public static ClientSessionModel restartSession(KeycloakSession session, RealmModel realm, String code) throws Exception { | ||
Cookie cook = session.getContext().getRequestHeaders().getCookies().get(KC_RESTART); | ||
if (cook == null) { | ||
logger.debug("KC_RESTART cookie doesn't exist"); | ||
return null; | ||
} | ||
String encodedCookie = cook.getValue(); | ||
JWSInput input = new JWSInput(encodedCookie); | ||
/* | ||
if (!RSAProvider.verify(input, realm.getPublicKey())) { | ||
logger.debug("Failed to verify encoded RestartLoginCookie"); | ||
return null; | ||
} | ||
*/ | ||
if (!HMACProvider.verify(input, (SecretKey)realm.getCodeSecretKey())) { | ||
logger.debug("Failed to verify encoded RestartLoginCookie"); | ||
return null; | ||
} | ||
RestartLoginCookie cookie = input.readJsonContent(RestartLoginCookie.class); | ||
String[] parts = code.split("\\."); | ||
String clientSessionId = parts[1]; | ||
if (!clientSessionId.equals(cookie.getClientSession())) { | ||
logger.debug("RestartLoginCookie clientSession does not match code's clientSession"); | ||
return null; | ||
} | ||
|
||
ClientModel client = realm.getClientByClientId(cookie.getClientId()); | ||
if (client == null) return null; | ||
|
||
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client); | ||
clientSession.setAuthMethod(cookie.getAuthMethod()); | ||
clientSession.setRedirectUri(cookie.getRedirectUri()); | ||
clientSession.setAction(cookie.getAction()); | ||
for (Map.Entry<String, String> entry : cookie.getNotes().entrySet()) { | ||
clientSession.setNote(entry.getKey(), entry.getValue()); | ||
} | ||
|
||
return clientSession; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.