KEYCLOAK-905

Realm key rotation for OIDC
This commit is contained in:
Stian Thorgersen 2016-10-05 13:06:54 +02:00
parent 1e51952006
commit d2cae0f8c3
161 changed files with 4905 additions and 2704 deletions

View file

@ -214,7 +214,7 @@ public class PreAuthActionsHandler {
try { try {
JWSInput input = new JWSInput(token); JWSInput input = new JWSInput(token);
PublicKey publicKey = AdapterRSATokenVerifier.getPublicKey(input, deployment); PublicKey publicKey = AdapterRSATokenVerifier.getPublicKey(input.getHeader().getKeyId(), deployment);
if (RSAProvider.verify(input, publicKey)) { if (RSAProvider.verify(input, publicKey)) {
return input; return input;
} }

View file

@ -38,12 +38,12 @@ public class AdapterRSATokenVerifier {
} }
public static PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment) throws VerificationException { public static PublicKey getPublicKey(String kid, KeycloakDeployment deployment) throws VerificationException {
PublicKeyLocator pkLocator = deployment.getPublicKeyLocator(); PublicKeyLocator pkLocator = deployment.getPublicKeyLocator();
PublicKey publicKey = pkLocator.getPublicKey(input, deployment); PublicKey publicKey = pkLocator.getPublicKey(kid, deployment);
if (publicKey == null) { if (publicKey == null) {
log.errorf("Didn't find publicKey for kid: %s", input.getHeader().getKeyId()); log.errorf("Didn't find publicKey for kid: %s", kid);
throw new VerificationException("Didn't find publicKey for specified kid"); throw new VerificationException("Didn't find publicKey for specified kid");
} }
@ -51,14 +51,8 @@ public class AdapterRSATokenVerifier {
} }
public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment, boolean checkActive, boolean checkTokenType) throws VerificationException { public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment, boolean checkActive, boolean checkTokenType) throws VerificationException {
JWSInput input; RSATokenVerifier verifier = RSATokenVerifier.create(tokenString).realmUrl(deployment.getRealmInfoUrl()).checkActive(checkActive).checkTokenType(checkTokenType);
try { PublicKey publicKey = getPublicKey(verifier.getHeader().getKeyId(), deployment);
input = new JWSInput(tokenString); return verifier.publicKey(publicKey).verify().getToken();
} catch (Exception e) {
throw new VerificationException("Couldn't parse token", e);
}
PublicKey publicKey = getPublicKey(input, deployment);
return RSATokenVerifier.verifyToken(input, publicKey, deployment.getRealmInfoUrl(), checkActive, checkTokenType);
} }
} }

View file

@ -34,7 +34,7 @@ public class HardcodedPublicKeyLocator implements PublicKeyLocator {
} }
@Override @Override
public PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment) { public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
return publicKey; return publicKey;
} }
} }

View file

@ -46,13 +46,7 @@ public class JWKPublicKeyLocator implements PublicKeyLocator {
private volatile int lastRequestTime = 0; private volatile int lastRequestTime = 0;
@Override @Override
public PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment) { public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
String kid = input.getHeader().getKeyId();
return getPublicKey(kid, deployment);
}
private PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
int minTimeBetweenRequests = deployment.getMinTimeBetweenJwksRequests(); int minTimeBetweenRequests = deployment.getMinTimeBetweenJwksRequests();
// Check if key is in cache. // Check if key is in cache.

View file

@ -28,10 +28,10 @@ import java.security.PublicKey;
public interface PublicKeyLocator { public interface PublicKeyLocator {
/** /**
* @param input * @param kid
* @param deployment * @param deployment
* @return publicKey, which should be used for verify signature on given "input" * @return publicKey, which should be used for verify signature on given "input"
*/ */
PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment); PublicKey getPublicKey(String kid, KeycloakDeployment deployment);
} }

View file

@ -140,11 +140,13 @@ public class CertificateUtils {
* *
* @throws Exception the exception * @throws Exception the exception
*/ */
public static X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject) throws Exception { public static X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject) {
return generateV1SelfSignedCertificate(caKeyPair, subject, BigInteger.valueOf(System.currentTimeMillis()));
}
public static X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject, BigInteger serialNumber) {
try { try {
X500Name subjectDN = new X500Name("CN=" + subject); X500Name subjectDN = new X500Name("CN=" + subject);
BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis());
Date validityStartDate = new Date(System.currentTimeMillis() - 100000); Date validityStartDate = new Date(System.currentTimeMillis() - 100000);
Calendar calendar = Calendar.getInstance(); Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.YEAR, 10); calendar.add(Calendar.YEAR, 10);

View file

@ -0,0 +1,76 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.common.util;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.spec.RSAPublicKeySpec;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class KeyUtils {
private static final String DEFAULT_MESSAGE_DIGEST = "SHA-256";
private KeyUtils() {
}
public static KeyPair generateRsaKeyPair(int keysize) {
try {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(keysize);
KeyPair keyPair = generator.generateKeyPair();
return keyPair;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static PublicKey extractPublicKey(PrivateKey key) {
if (key == null) {
return null;
}
try {
RSAPrivateCrtKey rsaPrivateCrtKey = (RSAPrivateCrtKey) key;
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(rsaPrivateCrtKey.getModulus(), rsaPrivateCrtKey.getPublicExponent());
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(publicKeySpec);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String createKeyId(Key key) {
try {
return Base64Url.encode(MessageDigest.getInstance(DEFAULT_MESSAGE_DIGEST).digest(key.getEncoded()));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -0,0 +1,29 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.common.util;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class PemException extends RuntimeException {
public PemException(Throwable cause) {
super(cause);
}
}

View file

@ -18,12 +18,15 @@
package org.keycloak.common.util; package org.keycloak.common.util;
import org.bouncycastle.openssl.PEMWriter;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.StringWriter;
import java.security.Key;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
/** /**
@ -33,6 +36,7 @@ import java.security.cert.X509Certificate;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public final class PemUtils { public final class PemUtils {
static { static {
BouncyIntegration.init(); BouncyIntegration.init();
} }
@ -40,73 +44,112 @@ public final class PemUtils {
private PemUtils() { private PemUtils() {
} }
public static X509Certificate decodeCertificate(InputStream is) throws Exception { /**
byte[] der = pemToDer(is); * Decode a X509 Certificate from a PEM string
ByteArrayInputStream bis = new ByteArrayInputStream(der); *
return DerUtils.decodeCertificate(bis); * @param cert
} * @return
* @throws Exception
*/
public static X509Certificate decodeCertificate(String cert) {
if (cert == null) {
return null;
}
public static X509Certificate decodeCertificate(String cert) throws Exception { try {
byte[] der = pemToDer(cert); byte[] der = pemToDer(cert);
ByteArrayInputStream bis = new ByteArrayInputStream(der); ByteArrayInputStream bis = new ByteArrayInputStream(der);
return DerUtils.decodeCertificate(bis); return DerUtils.decodeCertificate(bis);
} catch (Exception e) {
throw new PemException(e);
}
} }
/** /**
* Extract a public key from a PEM string * Decode a Public Key from a PEM string
* *
* @param pem * @param pem
* @return * @return
* @throws Exception * @throws Exception
*/ */
public static PublicKey decodePublicKey(String pem) throws Exception { public static PublicKey decodePublicKey(String pem) {
byte[] der = pemToDer(pem); if (pem == null) {
return DerUtils.decodePublicKey(der); return null;
}
try {
byte[] der = pemToDer(pem);
return DerUtils.decodePublicKey(der);
} catch (Exception e) {
throw new PemException(e);
}
} }
/** /**
* Extract a private key that is a PKCS8 pem string (base64 encoded PKCS8) * Decode a Private Key from a PEM string
* *
* @param pem * @param pem
* @return * @return
* @throws Exception * @throws Exception
*/ */
public static PrivateKey decodePrivateKey(String pem) throws Exception { public static PrivateKey decodePrivateKey(String pem) {
byte[] der = pemToDer(pem); if (pem == null) {
return DerUtils.decodePrivateKey(der); return null;
} }
public static PrivateKey decodePrivateKey(InputStream is) throws Exception { try {
String pem = pemFromStream(is); byte[] der = pemToDer(pem);
return decodePrivateKey(pem); return DerUtils.decodePrivateKey(der);
} catch (Exception e) {
throw new PemException(e);
}
} }
/** /**
* Decode a PEM file to DER format * Encode a Key to a PEM string
* *
* @param is * @param key
* @return * @return
* @throws java.io.IOException * @throws Exception
*/ */
public static byte[] pemToDer(InputStream is) throws IOException { public static String encodeKey(Key key) {
String pem = pemFromStream(is); return encode(key);
return pemToDer(pem);
} }
/** /**
* Decode a PEM string to DER format * Encode a X509 Certificate to a PEM string
* *
* @param pem * @param certificate
* @return * @return
* @throws java.io.IOException
*/ */
public static byte[] pemToDer(String pem) throws IOException { public static String encodeCertificate(Certificate certificate) {
return encode(certificate);
}
private static String encode(Object obj) {
if (obj == null) {
return null;
}
try {
StringWriter writer = new StringWriter();
PEMWriter pemWriter = new PEMWriter(writer);
pemWriter.writeObject(obj);
pemWriter.flush();
pemWriter.close();
String s = writer.toString();
return PemUtils.removeBeginEnd(s);
} catch (Exception e) {
throw new PemException(e);
}
}
private static byte[] pemToDer(String pem) throws IOException {
pem = removeBeginEnd(pem); pem = removeBeginEnd(pem);
return Base64.decode(pem); return Base64.decode(pem);
} }
public static String removeBeginEnd(String pem) { private static String removeBeginEnd(String pem) {
pem = pem.replaceAll("-----BEGIN (.*)-----", ""); pem = pem.replaceAll("-----BEGIN (.*)-----", "");
pem = pem.replaceAll("-----END (.*)----", ""); pem = pem.replaceAll("-----END (.*)----", "");
pem = pem.replaceAll("\r\n", ""); pem = pem.replaceAll("\r\n", "");
@ -114,12 +157,4 @@ public final class PemUtils {
return pem.trim(); return pem.trim();
} }
public static String pemFromStream(InputStream is) throws IOException {
DataInputStream dis = new DataInputStream(is);
byte[] keyBytes = new byte[dis.available()];
dis.readFully(keyBytes);
dis.close();
return new String(keyBytes, "utf-8");
}
} }

View file

@ -18,6 +18,7 @@
package org.keycloak; package org.keycloak;
import org.keycloak.common.VerificationException; import org.keycloak.common.VerificationException;
import org.keycloak.jose.jws.JWSHeader;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException; import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.RSAProvider; import org.keycloak.jose.jws.crypto.RSAProvider;
@ -31,84 +32,123 @@ import java.security.PublicKey;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class RSATokenVerifier { public class RSATokenVerifier {
public static AccessToken verifyToken(String tokenString, PublicKey realmKey, String realmUrl) throws VerificationException {
return verifyToken(tokenString, realmKey, realmUrl, true, true); private final String tokenString;
private PublicKey publicKey;
private String realmUrl;
private boolean checkTokenType = true;
private boolean checkActive = true;
private boolean checkRealmUrl = true;
private JWSInput jws;
private AccessToken token;
private RSATokenVerifier(String tokenString) {
this.tokenString = tokenString;
} }
public static AccessToken verifyToken(String tokenString, PublicKey realmKey, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException { public static RSATokenVerifier create(String tokenString) {
AccessToken token = toAccessToken(tokenString, realmKey); return new RSATokenVerifier(tokenString);
tokenVerifications(token, realmUrl, checkActive, checkTokenType);
return token;
} }
private static void tokenVerifications(AccessToken token, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException { public static AccessToken verifyToken(String tokenString, PublicKey publicKey, String realmUrl) throws VerificationException {
String user = token.getSubject(); return RSATokenVerifier.create(tokenString).publicKey(publicKey).realmUrl(realmUrl).verify().getToken();
if (user == null) { }
throw new VerificationException("Token user was null.");
}
if (realmUrl == null) {
throw new VerificationException("Realm URL is null. Make sure to add auth-server-url to the configuration of your adapter!");
}
if (!realmUrl.equals(token.getIssuer())) {
throw new VerificationException("Token audience doesn't match domain. Token issuer is " + token.getIssuer() + ", but URL from configuration is " + realmUrl);
} public static AccessToken verifyToken(String tokenString, PublicKey publicKey, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException {
return RSATokenVerifier.create(tokenString).publicKey(publicKey).realmUrl(realmUrl).checkActive(checkActive).checkTokenType(checkTokenType).verify().getToken();
}
if (checkTokenType) { public RSATokenVerifier publicKey(PublicKey publicKey) {
String type = token.getType(); this.publicKey = publicKey;
if (type == null || !type.equalsIgnoreCase(TokenUtil.TOKEN_TYPE_BEARER)) { return this;
throw new VerificationException("Token type is incorrect. Expected '" + TokenUtil.TOKEN_TYPE_BEARER + "' but was '" + type + "'"); }
public RSATokenVerifier realmUrl(String realmUrl) {
this.realmUrl = realmUrl;
return this;
}
public RSATokenVerifier checkTokenType(boolean checkTokenType) {
this.checkTokenType = checkTokenType;
return this;
}
public RSATokenVerifier checkActive(boolean checkActive) {
this.checkActive = checkActive;
return this;
}
public RSATokenVerifier checkRealmUrl(boolean checkRealmUrl) {
this.checkRealmUrl = checkRealmUrl;
return this;
}
public RSATokenVerifier parse() throws VerificationException {
if (jws == null) {
if (tokenString == null) {
throw new VerificationException("Token not set");
}
try {
jws = new JWSInput(tokenString);
} catch (JWSInputException e) {
throw new VerificationException("Failed to parse JWT", e);
}
try {
token = jws.readJsonContent(AccessToken.class);
} catch (JWSInputException e) {
throw new VerificationException("Failed to read access token from JWT", e);
} }
} }
return this;
}
public AccessToken getToken() throws VerificationException {
parse();
return token;
}
public JWSHeader getHeader() throws VerificationException {
parse();
return jws.getHeader();
}
public RSATokenVerifier verify() throws VerificationException {
parse();
if (publicKey == null) {
throw new VerificationException("Public key not set");
}
if (checkRealmUrl && realmUrl == null) {
throw new VerificationException("Realm URL not set");
}
if (!RSAProvider.verify(jws, publicKey)) {
throw new VerificationException("Invalid token signature");
}
String user = token.getSubject();
if (user == null) {
throw new VerificationException("Subject missing in token");
}
if (checkRealmUrl && !realmUrl.equals(token.getIssuer())) {
throw new VerificationException("Invalid token issuer. Expected '" + realmUrl + "', but was '" + token.getIssuer() + "'");
}
if (checkTokenType && !TokenUtil.TOKEN_TYPE_BEARER.equalsIgnoreCase(token.getType())) {
throw new VerificationException("Token type is incorrect. Expected '" + TokenUtil.TOKEN_TYPE_BEARER + "' but was '" + token.getType() + "'");
}
if (checkActive && !token.isActive()) { if (checkActive && !token.isActive()) {
throw new VerificationException("Token is not active."); throw new VerificationException("Token is not active");
} }
return this;
} }
public static AccessToken toAccessToken(String tokenString, PublicKey realmKey) throws VerificationException {
JWSInput input;
try {
input = new JWSInput(tokenString);
} catch (JWSInputException e) {
throw new VerificationException("Couldn't parse token", e);
}
if (!isPublicKeyValid(input, realmKey)) throw new VerificationException("Invalid token signature.");
AccessToken token;
try {
token = input.readJsonContent(AccessToken.class);
} catch (JWSInputException e) {
throw new VerificationException("Couldn't parse token signature", e);
}
return token;
}
public static AccessToken verifyToken(JWSInput input, PublicKey realmKey, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException {
if (!isPublicKeyValid(input, realmKey)) throw new VerificationException("Invalid token signature.");
AccessToken token;
try {
token = input.readJsonContent(AccessToken.class);
} catch (JWSInputException e) {
throw new VerificationException("Couldn't parse token signature", e);
}
tokenVerifications(token, realmUrl, checkActive, checkTokenType);
return token;
}
private static boolean isPublicKeyValid(JWSInput input, PublicKey realmKey) throws VerificationException {
try {
return RSAProvider.verify(input, realmKey);
} catch (Exception e) {
throw new VerificationException("Token signature not validated.", e);
}
}
} }

View file

@ -18,6 +18,7 @@
package org.keycloak.jose.jwk; package org.keycloak.jose.jwk;
import org.keycloak.common.util.Base64Url; import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.KeyUtils;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.Key; import java.security.Key;
@ -53,7 +54,7 @@ public class JWKBuilder {
RSAPublicJWK k = new RSAPublicJWK(); RSAPublicJWK k = new RSAPublicJWK();
String kid = this.kid != null ? this.kid : createKeyId(key); String kid = this.kid != null ? this.kid : KeyUtils.createKeyId(key);
k.setKeyId(kid); k.setKeyId(kid);
k.setKeyType(RSAPublicJWK.RSA); k.setKeyType(RSAPublicJWK.RSA);
k.setAlgorithm(RSAPublicJWK.RS256); k.setAlgorithm(RSAPublicJWK.RS256);
@ -64,14 +65,6 @@ public class JWKBuilder {
return k; return k;
} }
public static String createKeyId(Key key) {
try {
return Base64Url.encode(MessageDigest.getInstance(DEFAULT_MESSAGE_DIGEST).digest(key.getEncoded()));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
/** /**
* Copied from org.apache.commons.codec.binary.Base64 * Copied from org.apache.commons.codec.binary.Base64
*/ */

View file

@ -24,6 +24,8 @@ import org.keycloak.common.util.MultivaluedHashMap;
*/ */
public class ComponentRepresentation { public class ComponentRepresentation {
public static final String SECRET_VALUE = "**********";
private String id; private String id;
private String name; private String name;
private String providerId; private String providerId;

View file

@ -27,6 +27,7 @@ public class ConfigPropertyRepresentation {
protected String helpText; protected String helpText;
protected String type; protected String type;
protected Object defaultValue; protected Object defaultValue;
protected boolean secret;
public String getName() { public String getName() {
return name; return name;
@ -67,4 +68,12 @@ public class ConfigPropertyRepresentation {
public void setHelpText(String helpText) { public void setHelpText(String helpText) {
this.helpText = helpText; this.helpText = helpText;
} }
public boolean isSecret() {
return secret;
}
public void setSecret(boolean secret) {
this.secret = secret;
}
} }

View file

@ -0,0 +1,117 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.representations.idm;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class KeysMetadataRepresentation {
private Map<String, String> active;
private List<KeyMetadataRepresentation> keys;
public Map<String, String> getActive() {
return active;
}
public void setActive(Map<String, String> active) {
this.active = active;
}
public List<KeyMetadataRepresentation> getKeys() {
return keys;
}
public void setKeys(List<KeyMetadataRepresentation> keys) {
this.keys = keys;
}
public static class KeyMetadataRepresentation {
private String providerId;
private long providerPriority;
private String kid;
private String status;
private String type;
private String publicKey;
private String certificate;
public String getProviderId() {
return providerId;
}
public void setProviderId(String providerId) {
this.providerId = providerId;
}
public long getProviderPriority() {
return providerPriority;
}
public void setProviderPriority(long providerPriority) {
this.providerPriority = providerPriority;
}
public String getKid() {
return kid;
}
public void setKid(String kid) {
this.kid = kid;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getPublicKey() {
return publicKey;
}
public void setPublicKey(String publicKey) {
this.publicKey = publicKey;
}
public String getCertificate() {
return certificate;
}
public void setCertificate(String certificate) {
this.certificate = certificate;
}
}
}

View file

@ -19,11 +19,8 @@ package org.keycloak.representations.idm;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import org.bouncycastle.openssl.PEMWriter;
import org.keycloak.common.util.PemUtils; import org.keycloak.common.util.PemUtils;
import java.io.IOException;
import java.io.StringWriter;
import java.security.PublicKey; import java.security.PublicKey;
/** /**
@ -85,17 +82,7 @@ public class PublishedRealmRepresentation {
@JsonIgnore @JsonIgnore
public void setPublicKey(PublicKey publicKey) { public void setPublicKey(PublicKey publicKey) {
this.publicKey = publicKey; this.publicKey = publicKey;
StringWriter writer = new StringWriter(); this.publicKeyPem = PemUtils.encodeKey(publicKey);
PEMWriter pemWriter = new PEMWriter(writer);
try {
pemWriter.writeObject(publicKey);
pemWriter.flush();
pemWriter.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
String s = writer.toString();
this.publicKeyPem = PemUtils.removeBeginEnd(s);
} }
public String getTokenServiceUrl() { public String getTokenServiceUrl() {

View file

@ -72,9 +72,13 @@ public class RealmRepresentation {
protected Integer failureFactor; protected Integer failureFactor;
//--- end brute force settings //--- end brute force settings
@Deprecated
protected String privateKey; protected String privateKey;
@Deprecated
protected String publicKey; protected String publicKey;
@Deprecated
protected String certificate; protected String certificate;
@Deprecated
protected String codeSecret; protected String codeSecret;
protected RolesRepresentation roles; protected RolesRepresentation roles;
protected List<GroupRepresentation> groups; protected List<GroupRepresentation> groups;

View file

@ -0,0 +1,36 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.admin.client.resource;
import org.keycloak.representations.idm.KeysMetadataRepresentation;
import javax.ws.rs.GET;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface KeyResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
KeysMetadataRepresentation getKeyMetadata();
}

View file

@ -207,4 +207,7 @@ public interface RealmResource {
@Path("components") @Path("components")
ComponentsResource components(); ComponentsResource components();
@Path("keys")
KeyResource keys();
} }

View file

@ -60,10 +60,6 @@ public class RealmAdapter implements RealmModel {
protected RealmCacheSession cacheSession; protected RealmCacheSession cacheSession;
protected RealmModel updated; protected RealmModel updated;
protected RealmCache cache; protected RealmCache cache;
protected volatile transient PublicKey publicKey;
protected volatile transient PrivateKey privateKey;
protected volatile transient Key codeSecretKey;
protected volatile transient X509Certificate certificate;
public RealmAdapter(CachedRealm cached, RealmCacheSession cacheSession) { public RealmAdapter(CachedRealm cached, RealmCacheSession cacheSession) {
this.cached = cached; this.cached = cached;
@ -423,123 +419,6 @@ public class RealmAdapter implements RealmModel {
updated.setAccessCodeLifespanLogin(seconds); updated.setAccessCodeLifespanLogin(seconds);
} }
@Override
public String getKeyId() {
if (isUpdated()) return updated.getKeyId();
return cached.getKeyId();
}
@Override
public String getPublicKeyPem() {
if (isUpdated()) return updated.getPublicKeyPem();
return cached.getPublicKeyPem();
}
@Override
public void setPublicKeyPem(String publicKeyPem) {
getDelegateForUpdate();
updated.setPublicKeyPem(publicKeyPem);
}
@Override
public String getPrivateKeyPem() {
if (isUpdated()) return updated.getPrivateKeyPem();
return cached.getPrivateKeyPem();
}
@Override
public void setPrivateKeyPem(String privateKeyPem) {
getDelegateForUpdate();
updated.setPrivateKeyPem(privateKeyPem);
}
@Override
public PublicKey getPublicKey() {
if (isUpdated()) return updated.getPublicKey();
if (publicKey != null) return publicKey;
publicKey = cached.getPublicKey();
if (publicKey != null) return publicKey;
publicKey = KeycloakModelUtils.getPublicKey(getPublicKeyPem());
return publicKey;
}
@Override
public void setPublicKey(PublicKey publicKey) {
this.publicKey = publicKey;
String publicKeyPem = KeycloakModelUtils.getPemFromKey(publicKey);
setPublicKeyPem(publicKeyPem);
}
@Override
public X509Certificate getCertificate() {
if (isUpdated()) return updated.getCertificate();
if (certificate != null) return certificate;
certificate = cached.getCertificate();
if (certificate != null) return certificate;
certificate = KeycloakModelUtils.getCertificate(getCertificatePem());
return certificate;
}
@Override
public void setCertificate(X509Certificate certificate) {
this.certificate = certificate;
String certPem = KeycloakModelUtils.getPemFromCertificate(certificate);
setCertificatePem(certPem);
}
@Override
public String getCertificatePem() {
if (isUpdated()) return updated.getCertificatePem();
return cached.getCertificatePem();
}
@Override
public void setCertificatePem(String certificate) {
getDelegateForUpdate();
updated.setCertificatePem(certificate);
}
@Override
public PrivateKey getPrivateKey() {
if (isUpdated()) return updated.getPrivateKey();
if (privateKey != null) {
return privateKey;
}
privateKey = cached.getPrivateKey();
if (privateKey != null) {
return privateKey;
}
privateKey = KeycloakModelUtils.getPrivateKey(getPrivateKeyPem());
return privateKey;
}
@Override
public void setPrivateKey(PrivateKey privateKey) {
this.privateKey = privateKey;
String privateKeyPem = KeycloakModelUtils.getPemFromKey(privateKey);
setPrivateKeyPem(privateKeyPem);
}
@Override
public String getCodeSecret() {
return isUpdated() ? updated.getCodeSecret() : cached.getCodeSecret();
}
@Override
public Key getCodeSecretKey() {
if (codeSecretKey == null) {
codeSecretKey = KeycloakModelUtils.getSecretKey(getCodeSecret());
}
return codeSecretKey;
}
@Override
public void setCodeSecret(String codeSecret) {
getDelegateForUpdate();
updated.setCodeSecret(codeSecret);
}
@Override @Override
public List<RequiredCredentialModel> getRequiredCredentials() { public List<RequiredCredentialModel> getRequiredCredentials() {
if (isUpdated()) return updated.getRequiredCredentials(); if (isUpdated()) return updated.getRequiredCredentials();

View file

@ -89,15 +89,6 @@ public class CachedRealm extends AbstractRevisioned {
protected PasswordPolicy passwordPolicy; protected PasswordPolicy passwordPolicy;
protected OTPPolicy otpPolicy; protected OTPPolicy otpPolicy;
protected transient String keyId;
protected transient PublicKey publicKey;
protected String publicKeyPem;
protected transient PrivateKey privateKey;
protected String privateKeyPem;
protected transient X509Certificate certificate;
protected String certificatePem;
protected String codeSecret;
protected String loginTheme; protected String loginTheme;
protected String accountTheme; protected String accountTheme;
protected String adminTheme; protected String adminTheme;
@ -191,15 +182,6 @@ public class CachedRealm extends AbstractRevisioned {
passwordPolicy = model.getPasswordPolicy(); passwordPolicy = model.getPasswordPolicy();
otpPolicy = model.getOTPPolicy(); otpPolicy = model.getOTPPolicy();
keyId = model.getKeyId();
publicKeyPem = model.getPublicKeyPem();
publicKey = model.getPublicKey();
privateKeyPem = model.getPrivateKeyPem();
privateKey = model.getPrivateKey();
certificatePem = model.getCertificatePem();
certificate = model.getCertificate();
codeSecret = model.getCodeSecret();
loginTheme = model.getLoginTheme(); loginTheme = model.getLoginTheme();
accountTheme = model.getAccountTheme(); accountTheme = model.getAccountTheme();
adminTheme = model.getAdminTheme(); adminTheme = model.getAdminTheme();
@ -415,22 +397,6 @@ public class CachedRealm extends AbstractRevisioned {
return accessCodeLifespanLogin; return accessCodeLifespanLogin;
} }
public String getKeyId() {
return keyId;
}
public String getPublicKeyPem() {
return publicKeyPem;
}
public String getPrivateKeyPem() {
return privateKeyPem;
}
public String getCodeSecret() {
return codeSecret;
}
public List<RequiredCredentialModel> getRequiredCredentials() { public List<RequiredCredentialModel> getRequiredCredentials() {
return requiredCredentials; return requiredCredentials;
} }
@ -507,10 +473,6 @@ public class CachedRealm extends AbstractRevisioned {
return userFederationMappers; return userFederationMappers;
} }
public String getCertificatePem() {
return certificatePem;
}
public List<IdentityProviderModel> getIdentityProviders() { public List<IdentityProviderModel> getIdentityProviders() {
return identityProviders; return identityProviders;
} }
@ -591,18 +553,6 @@ public class CachedRealm extends AbstractRevisioned {
return clientTemplates; return clientTemplates;
} }
public PublicKey getPublicKey() {
return publicKey;
}
public PrivateKey getPrivateKey() {
return privateKey;
}
public X509Certificate getCertificate() {
return certificate;
}
public Set<UserFederationMapperModel> getUserFederationMapperSet() { public Set<UserFederationMapperModel> getUserFederationMapperSet() {
return userFederationMapperSet; return userFederationMapperSet;
} }

View file

@ -106,7 +106,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
tx.put(sessionCache, id, entity); tx.put(sessionCache, id, entity);
ClientSessionAdapter wrap = wrap(realm, entity, false); ClientSessionAdapter wrap = wrap(realm, entity, false);
wrap.setNote(ClientSessionModel.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
return wrap; return wrap;
} }

View file

@ -0,0 +1,88 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.connections.jpa.updater.liquibase.custom;
import liquibase.exception.CustomChangeException;
import liquibase.statement.core.InsertStatement;
import liquibase.structure.core.Table;
import org.keycloak.keys.KeyProvider;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ExtractRealmKeysFromRealmTable extends CustomKeycloakTask {
@Override
protected void generateStatementsImpl() throws CustomChangeException {
try {
PreparedStatement statement = jdbcConnection.prepareStatement("select ID, PRIVATE_KEY, CERTIFICATE from " + getTableName("REALM"));
try {
ResultSet resultSet = statement.executeQuery();
try {
while (resultSet.next()) {
String realmId = resultSet.getString(1);
String privateKeyPem = resultSet.getString(2);
String certificatePem = resultSet.getString(3);
String componentId = KeycloakModelUtils.generateId();
InsertStatement insertComponent = new InsertStatement(null, null, database.correctObjectName("COMPONENT", Table.class))
.addColumnValue("ID", componentId)
.addColumnValue("REALM_ID", realmId)
.addColumnValue("PARENT_ID", realmId)
.addColumnValue("NAME", "rsa")
.addColumnValue("PROVIDER_ID", "rsa")
.addColumnValue("PROVIDER_TYPE", KeyProvider.class.getName());
statements.add(insertComponent);
statements.add(componentConfigStatement(componentId, "priority", "100"));
statements.add(componentConfigStatement(componentId, "privateKey", privateKeyPem));
statements.add(componentConfigStatement(componentId, "certificate", certificatePem));
}
} finally {
resultSet.close();
}
} finally {
statement.close();
}
confirmationMessage.append("Updated " + statements.size() + " records in USER_FEDERATION_PROVIDER table");
} catch (Exception e) {
throw new CustomChangeException(getTaskId() + ": Exception when updating data from previous version", e);
}
}
private InsertStatement componentConfigStatement(String componentId, String name, String value) {
return new InsertStatement(null, null, database.correctObjectName("COMPONENT_CONFIG", Table.class))
.addColumnValue("ID", KeycloakModelUtils.generateId())
.addColumnValue("COMPONENT_ID", componentId)
.addColumnValue("NAME", name)
.addColumnValue("VALUE", value);
}
@Override
protected String getTaskId() {
return "Update 2.3.0.Final";
}
}

View file

@ -20,6 +20,7 @@ package org.keycloak.models.jpa;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.common.enums.SslRequired; import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentFactory;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
import org.keycloak.jose.jwk.JWKBuilder; import org.keycloak.jose.jwk.JWKBuilder;
import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.AuthenticationExecutionModel;
@ -60,6 +61,7 @@ import org.keycloak.models.jpa.entities.RequiredCredentialEntity;
import org.keycloak.models.jpa.entities.RoleEntity; import org.keycloak.models.jpa.entities.RoleEntity;
import org.keycloak.models.jpa.entities.UserFederationMapperEntity; import org.keycloak.models.jpa.entities.UserFederationMapperEntity;
import org.keycloak.models.jpa.entities.UserFederationProviderEntity; import org.keycloak.models.jpa.entities.UserFederationProviderEntity;
import org.keycloak.models.utils.ComponentUtil;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
@ -88,10 +90,6 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
protected static final Logger logger = Logger.getLogger(RealmAdapter.class); protected static final Logger logger = Logger.getLogger(RealmAdapter.class);
protected RealmEntity realm; protected RealmEntity realm;
protected EntityManager em; protected EntityManager em;
protected volatile transient PublicKey publicKey;
protected volatile transient PrivateKey privateKey;
protected volatile transient X509Certificate certificate;
protected volatile transient Key codeSecretKey;
protected KeycloakSession session; protected KeycloakSession session;
private PasswordPolicy passwordPolicy; private PasswordPolicy passwordPolicy;
private OTPPolicy otpPolicy; private OTPPolicy otpPolicy;
@ -488,106 +486,6 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
em.flush(); em.flush();
} }
@Override
public String getKeyId() {
PublicKey publicKey = getPublicKey();
return publicKey != null ? JWKBuilder.create().rs256(publicKey).getKeyId() : null;
}
@Override
public String getPublicKeyPem() {
return realm.getPublicKeyPem();
}
@Override
public void setPublicKeyPem(String publicKeyPem) {
realm.setPublicKeyPem(publicKeyPem);
em.flush();
}
@Override
public X509Certificate getCertificate() {
if (certificate != null) return certificate;
certificate = KeycloakModelUtils.getCertificate(getCertificatePem());
return certificate;
}
@Override
public void setCertificate(X509Certificate certificate) {
this.certificate = certificate;
String certificatePem = KeycloakModelUtils.getPemFromCertificate(certificate);
setCertificatePem(certificatePem);
}
@Override
public String getCertificatePem() {
return realm.getCertificatePem();
}
@Override
public void setCertificatePem(String certificate) {
realm.setCertificatePem(certificate);
}
@Override
public String getPrivateKeyPem() {
return realm.getPrivateKeyPem();
}
@Override
public void setPrivateKeyPem(String privateKeyPem) {
realm.setPrivateKeyPem(privateKeyPem);
em.flush();
}
@Override
public PublicKey getPublicKey() {
if (publicKey != null) return publicKey;
publicKey = KeycloakModelUtils.getPublicKey(getPublicKeyPem());
return publicKey;
}
@Override
public void setPublicKey(PublicKey publicKey) {
this.publicKey = publicKey;
String publicKeyPem = KeycloakModelUtils.getPemFromKey(publicKey);
setPublicKeyPem(publicKeyPem);
}
@Override
public PrivateKey getPrivateKey() {
if (privateKey != null) return privateKey;
privateKey = KeycloakModelUtils.getPrivateKey(getPrivateKeyPem());
return privateKey;
}
@Override
public void setPrivateKey(PrivateKey privateKey) {
this.privateKey = privateKey;
String privateKeyPem = KeycloakModelUtils.getPemFromKey(privateKey);
setPrivateKeyPem(privateKeyPem);
}
@Override
public String getCodeSecret() {
return realm.getCodeSecret();
}
@Override
public Key getCodeSecretKey() {
if (codeSecretKey == null) {
codeSecretKey = KeycloakModelUtils.getSecretKey(getCodeSecret());
}
return codeSecretKey;
}
@Override
public void setCodeSecret(String codeSecret) {
realm.setCodeSecret(codeSecret);
}
protected RequiredCredentialModel initRequiredCredentialModel(String type) { protected RequiredCredentialModel initRequiredCredentialModel(String type) {
RequiredCredentialModel model = RequiredCredentialModel.BUILT_IN.get(type); RequiredCredentialModel model = RequiredCredentialModel.BUILT_IN.get(type);
if (model == null) { if (model == null) {
@ -2138,6 +2036,13 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
@Override @Override
public ComponentModel addComponentModel(ComponentModel model) { public ComponentModel addComponentModel(ComponentModel model) {
ComponentFactory componentFactory = ComponentUtil.getComponentFactory(session, model);
if (componentFactory == null) {
throw new IllegalArgumentException("Invalid component type");
}
componentFactory.validateConfiguration(session, model);
ComponentEntity c = new ComponentEntity(); ComponentEntity c = new ComponentEntity();
if (model.getId() == null) { if (model.getId() == null) {
c.setId(KeycloakModelUtils.generateId()); c.setId(KeycloakModelUtils.generateId());
@ -2171,6 +2076,8 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
@Override @Override
public void updateComponent(ComponentModel component) { public void updateComponent(ComponentModel component) {
ComponentUtil.getComponentFactory(session, component).validateConfiguration(session, component);
ComponentEntity c = em.find(ComponentEntity.class, component.getId()); ComponentEntity c = em.find(ComponentEntity.class, component.getId());
if (c == null) return; if (c == null) return;
c.setName(component.getName()); c.setName(component.getName());

View file

@ -117,15 +117,6 @@ public class RealmEntity {
@Column(name="NOT_BEFORE") @Column(name="NOT_BEFORE")
protected int notBefore; protected int notBefore;
@Column(name="PUBLIC_KEY", length = 4000)
protected String publicKeyPem;
@Column(name="PRIVATE_KEY", length = 4000)
protected String privateKeyPem;
@Column(name="CERTIFICATE", length = 4000)
protected String certificatePem;
@Column(name="CODE_SECRET", length = 255)
protected String codeSecret;
@Column(name="LOGIN_THEME") @Column(name="LOGIN_THEME")
protected String loginTheme; protected String loginTheme;
@Column(name="ACCOUNT_THEME") @Column(name="ACCOUNT_THEME")
@ -384,30 +375,6 @@ public class RealmEntity {
this.accessCodeLifespanLogin = accessCodeLifespanLogin; this.accessCodeLifespanLogin = accessCodeLifespanLogin;
} }
public String getPublicKeyPem() {
return publicKeyPem;
}
public void setPublicKeyPem(String publicKeyPem) {
this.publicKeyPem = publicKeyPem;
}
public String getPrivateKeyPem() {
return privateKeyPem;
}
public void setPrivateKeyPem(String privateKeyPem) {
this.privateKeyPem = privateKeyPem;
}
public String getCodeSecret() {
return codeSecret;
}
public void setCodeSecret(String codeSecret) {
this.codeSecret = codeSecret;
}
public Collection<RequiredCredentialEntity> getRequiredCredentials() { public Collection<RequiredCredentialEntity> getRequiredCredentials() {
return requiredCredentials; return requiredCredentials;
} }
@ -567,14 +534,6 @@ public class RealmEntity {
this.attributes = attributes; this.attributes = attributes;
} }
public String getCertificatePem() {
return certificatePem;
}
public void setCertificatePem(String certificatePem) {
this.certificatePem = certificatePem;
}
public List<IdentityProviderEntity> getIdentityProviders() { public List<IdentityProviderEntity> getIdentityProviders() {
return this.identityProviders; return this.identityProviders;
} }

View file

@ -36,6 +36,13 @@
<addColumn tableName="IDENTITY_PROVIDER"> <addColumn tableName="IDENTITY_PROVIDER">
<column name="PROVIDER_DISPLAY_NAME" type="VARCHAR(255)"></column> <column name="PROVIDER_DISPLAY_NAME" type="VARCHAR(255)"></column>
</addColumn> </addColumn>
<customChange class="org.keycloak.connections.jpa.updater.liquibase.custom.ExtractRealmKeysFromRealmTable"/>
<dropColumn tableName="REALM" columnName="CODE_SECRET" />
<dropColumn tableName="REALM" columnName="PRIVATE_KEY" />
<dropColumn tableName="REALM" columnName="PUBLIC_KEY" />
<dropColumn tableName="REALM" columnName="CERTIFICATE" />
</changeSet> </changeSet>

View file

@ -33,6 +33,7 @@ import org.keycloak.connections.mongo.updater.impl.updates.Update1_4_0;
import org.keycloak.connections.mongo.updater.impl.updates.Update1_7_0; import org.keycloak.connections.mongo.updater.impl.updates.Update1_7_0;
import org.keycloak.connections.mongo.updater.impl.updates.Update1_8_0; import org.keycloak.connections.mongo.updater.impl.updates.Update1_8_0;
import org.keycloak.connections.mongo.updater.impl.updates.Update1_9_2; import org.keycloak.connections.mongo.updater.impl.updates.Update1_9_2;
import org.keycloak.connections.mongo.updater.impl.updates.Update2_3_0;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import java.util.Date; import java.util.Date;
@ -57,7 +58,8 @@ public class DefaultMongoUpdaterProvider implements MongoUpdaterProvider {
Update1_4_0.class, Update1_4_0.class,
Update1_7_0.class, Update1_7_0.class,
Update1_8_0.class, Update1_8_0.class,
Update1_9_2.class Update1_9_2.class,
Update2_3_0.class
}; };
@Override @Override

View file

@ -0,0 +1,80 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.connections.mongo.updater.impl.updates;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import org.keycloak.keys.KeyProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.mongo.keycloak.entities.ComponentEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.Collections;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class Update2_3_0 extends Update {
@Override
public String getId() {
return "2.3.0";
}
@Override
public void update(KeycloakSession session) {
DBCollection realms = db.getCollection("realms");
DBCursor cursor = realms.find();
while (cursor.hasNext()) {
BasicDBObject realm = (BasicDBObject) cursor.next();
String realmId = realm.getString("_id");
String privateKeyPem = realm.getString("privateKeyPem");
String certificatePem = realm.getString("certificatePem");
BasicDBList entities = (BasicDBList) realm.get("componentEntities");
BasicDBObject component = new BasicDBObject();
component.put("id", KeycloakModelUtils.generateId());
component.put("name", "rsa");
component.put("providerType", KeyProvider.class.getName());
component.put("providerId", "rsa");
component.put("parentId", realmId);
BasicDBObject config = new BasicDBObject();
config.put("priority", Collections.singletonList("100"));
config.put("privateKey", Collections.singletonList(privateKeyPem));
config.put("certificate", Collections.singletonList(certificatePem));
component.put("config", config);
entities.add(component);
realm.remove("privateKeyPem");
realm.remove("certificatePem");
realm.remove("publicKeyPem");
realm.remove("codeSecret");
realms.update(new BasicDBObject().append("_id", realmId), realm);
}
}
}

View file

@ -59,6 +59,7 @@ import org.keycloak.models.mongo.keycloak.entities.RequiredActionProviderEntity;
import org.keycloak.models.mongo.keycloak.entities.RequiredCredentialEntity; import org.keycloak.models.mongo.keycloak.entities.RequiredCredentialEntity;
import org.keycloak.models.mongo.keycloak.entities.UserFederationMapperEntity; import org.keycloak.models.mongo.keycloak.entities.UserFederationMapperEntity;
import org.keycloak.models.mongo.keycloak.entities.UserFederationProviderEntity; import org.keycloak.models.mongo.keycloak.entities.UserFederationProviderEntity;
import org.keycloak.models.utils.ComponentUtil;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import java.security.Key; import java.security.Key;
@ -85,11 +86,6 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
private final MongoRealmEntity realm; private final MongoRealmEntity realm;
private final RealmProvider model; private final RealmProvider model;
protected volatile transient PublicKey publicKey;
protected volatile transient PrivateKey privateKey;
protected volatile transient X509Certificate certificate;
protected volatile transient Key codeSecretKey;
private volatile transient OTPPolicy otpPolicy; private volatile transient OTPPolicy otpPolicy;
private volatile transient PasswordPolicy passwordPolicy; private volatile transient PasswordPolicy passwordPolicy;
private volatile transient KeycloakSession session; private volatile transient KeycloakSession session;
@ -455,110 +451,6 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
return realm.getAccessCodeLifespanLogin(); return realm.getAccessCodeLifespanLogin();
} }
@Override
public String getKeyId() {
PublicKey publicKey = getPublicKey();
return publicKey != null ? JWKBuilder.create().rs256(publicKey).getKeyId() : null;
}
@Override
public String getPublicKeyPem() {
return realm.getPublicKeyPem();
}
@Override
public void setPublicKeyPem(String publicKeyPem) {
realm.setPublicKeyPem(publicKeyPem);
this.publicKey = null;
updateRealm();
}
@Override
public X509Certificate getCertificate() {
if (certificate != null) return certificate;
certificate = KeycloakModelUtils.getCertificate(getCertificatePem());
return certificate;
}
@Override
public void setCertificate(X509Certificate certificate) {
this.certificate = certificate;
String certificatePem = KeycloakModelUtils.getPemFromCertificate(certificate);
setCertificatePem(certificatePem);
}
@Override
public String getCertificatePem() {
return realm.getCertificatePem();
}
@Override
public void setCertificatePem(String certificate) {
realm.setCertificatePem(certificate);
}
@Override
public String getPrivateKeyPem() {
return realm.getPrivateKeyPem();
}
@Override
public void setPrivateKeyPem(String privateKeyPem) {
realm.setPrivateKeyPem(privateKeyPem);
this.privateKey = null;
updateRealm();
}
@Override
public PublicKey getPublicKey() {
if (publicKey != null) return publicKey;
publicKey = KeycloakModelUtils.getPublicKey(getPublicKeyPem());
return publicKey;
}
@Override
public void setPublicKey(PublicKey publicKey) {
this.publicKey = publicKey;
String publicKeyPem = KeycloakModelUtils.getPemFromKey(publicKey);
setPublicKeyPem(publicKeyPem);
}
@Override
public PrivateKey getPrivateKey() {
if (privateKey != null) return privateKey;
privateKey = KeycloakModelUtils.getPrivateKey(getPrivateKeyPem());
return privateKey;
}
@Override
public void setPrivateKey(PrivateKey privateKey) {
this.privateKey = privateKey;
String privateKeyPem = KeycloakModelUtils.getPemFromKey(privateKey);
setPrivateKeyPem(privateKeyPem);
}
@Override
public String getCodeSecret() {
return realm.getCodeSecret();
}
@Override
public Key getCodeSecretKey() {
if (codeSecretKey == null) {
codeSecretKey = KeycloakModelUtils.getSecretKey(getCodeSecret());
}
return codeSecretKey;
}
@Override
public void setCodeSecret(String codeSecret) {
realm.setCodeSecret(codeSecret);
updateRealm();
}
@Override @Override
public String getLoginTheme() { public String getLoginTheme() {
return realm.getLoginTheme(); return realm.getLoginTheme();
@ -2062,6 +1954,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
@Override @Override
public ComponentModel addComponentModel(ComponentModel model) { public ComponentModel addComponentModel(ComponentModel model) {
ComponentUtil.getComponentFactory(session, model).validateConfiguration(session, model);
ComponentEntity entity = new ComponentEntity(); ComponentEntity entity = new ComponentEntity();
if (model.getId() == null) { if (model.getId() == null) {
entity.setId(KeycloakModelUtils.generateId()); entity.setId(KeycloakModelUtils.generateId());
@ -2082,6 +1976,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
@Override @Override
public void updateComponent(ComponentModel model) { public void updateComponent(ComponentModel model) {
ComponentUtil.getComponentFactory(session, model).validateConfiguration(session, model);
for (ComponentEntity entity : realm.getComponentEntities()) { for (ComponentEntity entity : realm.getComponentEntities()) {
if (entity.getId().equals(model.getId())) { if (entity.getId().equals(model.getId())) {
entity.setConfig(model.getConfig()); entity.setConfig(model.getConfig());

View file

@ -70,11 +70,6 @@ public class RealmEntity extends AbstractIdentifiableEntity {
private int accessCodeLifespanLogin; private int accessCodeLifespanLogin;
private int notBefore; private int notBefore;
private String publicKeyPem;
private String privateKeyPem;
private String certificatePem;
private String codeSecret;
private String loginTheme; private String loginTheme;
private String accountTheme; private String accountTheme;
private String adminTheme; private String adminTheme;
@ -351,30 +346,6 @@ public class RealmEntity extends AbstractIdentifiableEntity {
this.notBefore = notBefore; this.notBefore = notBefore;
} }
public String getPublicKeyPem() {
return publicKeyPem;
}
public void setPublicKeyPem(String publicKeyPem) {
this.publicKeyPem = publicKeyPem;
}
public String getPrivateKeyPem() {
return privateKeyPem;
}
public void setPrivateKeyPem(String privateKeyPem) {
this.privateKeyPem = privateKeyPem;
}
public String getCodeSecret() {
return codeSecret;
}
public void setCodeSecret(String codeSecret) {
this.codeSecret = codeSecret;
}
public String getLoginTheme() { public String getLoginTheme() {
return loginTheme; return loginTheme;
} }
@ -527,14 +498,6 @@ public class RealmEntity extends AbstractIdentifiableEntity {
this.identityProviders = identityProviders; this.identityProviders = identityProviders;
} }
public String getCertificatePem() {
return certificatePem;
}
public void setCertificatePem(String certificatePem) {
this.certificatePem = certificatePem;
}
public boolean isInternationalizationEnabled() { public boolean isInternationalizationEnabled() {
return internationalizationEnabled; return internationalizationEnabled;
} }

View file

@ -33,6 +33,6 @@ public interface ComponentFactory<CreatedType, ProviderType extends Provider> ex
return null; return null;
} }
void validateConfiguration(KeycloakSession session, ComponentModel config) throws ComponentValidationException; void validateConfiguration(KeycloakSession session, ComponentModel model) throws ComponentValidationException;
} }

View file

@ -20,6 +20,7 @@ package org.keycloak.component;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import java.io.Serializable; import java.io.Serializable;
import java.util.concurrent.ConcurrentHashMap;
/** /**
* Stored configuration of a User Storage provider instance. * Stored configuration of a User Storage provider instance.
@ -35,6 +36,7 @@ public class ComponentModel implements Serializable {
private String providerType; private String providerType;
private String parentId; private String parentId;
private MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>(); private MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
private transient ConcurrentHashMap<String, Object> notes = new ConcurrentHashMap<>();
public ComponentModel() {} public ComponentModel() {}
@ -71,6 +73,57 @@ public class ComponentModel implements Serializable {
this.config = config; this.config = config;
} }
public boolean contains(String key) {
return config.containsKey(key);
}
public String get(String key) {
return config.getFirst(key);
}
public int get(String key, int defaultValue) {
String s = config.getFirst(key);
return s != null ? Integer.parseInt(s) : defaultValue;
}
public long get(String key, long defaultValue) {
String s = config.getFirst(key);
return s != null ? Long.parseLong(s) : defaultValue;
}
public boolean get(String key, boolean defaultValue) {
String s = config.getFirst(key);
return s != null ? Boolean.parseBoolean(s) : defaultValue;
}
public void put(String key, String value) {
config.putSingle(key, value);
}
public void put(String key, int value) {
config.putSingle(key, Integer.toString(value));
}
public void put(String key, long value) {
config.putSingle(key, Long.toString(value));
}
public void put(String key, boolean value) {
config.putSingle(key, Boolean.toString(value));
}
public boolean hasNote(String key) {
return notes.containsKey(key);
}
public <T> T getNote(String key) {
return (T) notes.get(key);
}
public void setNote(String key, Object object) {
notes.put(key, object);
}
public String getProviderId() { public String getProviderId() {
return providerId; return providerId;
} }

View file

@ -0,0 +1,104 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.keys;
import java.security.PublicKey;
import java.security.cert.Certificate;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class KeyMetadata {
public enum Status {
ACTIVE, PASSIVE, DISABLED
}
public enum Type {
RSA
}
private String providerId;
private long providerPriority;
private String kid;
private Status status;
private Type type;
private PublicKey publicKey;
private Certificate certificate;
public String getProviderId() {
return providerId;
}
public void setProviderId(String providerId) {
this.providerId = providerId;
}
public long getProviderPriority() {
return providerPriority;
}
public void setProviderPriority(long providerPriority) {
this.providerPriority = providerPriority;
}
public String getKid() {
return kid;
}
public void setKid(String kid) {
this.kid = kid;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public Type getType() {
return type;
}
public void setType(Type type) {
this.type = type;
}
public PublicKey getPublicKey() {
return publicKey;
}
public void setPublicKey(PublicKey publicKey) {
this.publicKey = publicKey;
}
public Certificate getCertificate() {
return certificate;
}
public void setCertificate(Certificate certificate) {
this.certificate = certificate;
}
}

View file

@ -0,0 +1,69 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.keys;
import org.keycloak.provider.Provider;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface KeyProvider extends Provider {
/**
* Return the KID for the active keypair, or <code>null</code> if no active key is available.
*
* @return
*/
String getKid();
/**
* Return the private key for the active keypair, or <code>null</code> if no active key is available.
*
* @return
*/
PrivateKey getPrivateKey();
/**
* Return the public key for the specified kid, or <code>null</code> if the kid is unknown.
*
* @param kid
* @return
*/
PublicKey getPublicKey(String kid);
/**
* Return the certificate for the specified kid, or <code>null</code> if the kid is unknown.
*
* @param kid
* @return
*/
X509Certificate getCertificate(String kid);
/**
* Return metadata about all keypairs held by the provider
* @return
*/
List<KeyMetadata> getKeyMetadata();
}

View file

@ -0,0 +1,31 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.keys;
import org.keycloak.component.ComponentFactory;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface KeyProviderFactory<T extends KeyProvider> extends ComponentFactory<T, KeyProvider> {
T create(KeycloakSession session, ComponentModel model);
}

View file

@ -15,50 +15,33 @@
* limitations under the License. * limitations under the License.
*/ */
package org.keycloak.representations; package org.keycloak.keys;
import org.keycloak.common.util.Time; import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public class PasswordToken { public class KeySpi implements Spi {
@Override
private String realm; public boolean isInternal() {
private String user; return true;
private int timestamp;
public PasswordToken() {
} }
public PasswordToken(String realm, String user) { @Override
this.realm = realm; public String getName() {
this.user = user; return "keys";
this.timestamp = Time.currentTime();
} }
public String getRealm() { @Override
return realm; public Class<? extends Provider> getProviderClass() {
return KeyProvider.class;
} }
public void setRealm(String realm) { @Override
this.realm = realm; public Class<? extends ProviderFactory> getProviderFactoryClass() {
return KeyProviderFactory.class;
} }
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public int getTimestamp() {
return timestamp;
}
public void setTimestamp(int timestamp) {
this.timestamp = timestamp;
}
} }

View file

@ -24,7 +24,7 @@ import java.util.Set;
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public interface ClientSessionModel { public interface ClientSessionModel {
public static final String ACTION_KEY = "action_key"; public static final String ACTION_SIGNATURE = "action_signature";
public String getId(); public String getId();
public RealmModel getRealm(); public RealmModel getRealm();

View file

@ -0,0 +1,71 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models;
import org.keycloak.keys.KeyMetadata;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface KeyManager {
ActiveKey getActiveKey(RealmModel realm);
PublicKey getPublicKey(RealmModel realm, String kid);
Certificate getCertificate(RealmModel realm, String kid);
List<KeyMetadata> getKeys(RealmModel realm, boolean includeDisabled);
class ActiveKey {
private final String kid;
private final PrivateKey privateKey;
private final PublicKey publicKey;
private final X509Certificate certificate;
public ActiveKey(String kid, PrivateKey privateKey, PublicKey publicKey, X509Certificate certificate) {
this.kid = kid;
this.privateKey = privateKey;
this.publicKey = publicKey;
this.certificate = certificate;
}
public String getKid() {
return kid;
}
public PrivateKey getPrivateKey() {
return privateKey;
}
public PublicKey getPublicKey() {
return publicKey;
}
public X509Certificate getCertificate() {
return certificate;
}
}
}

View file

@ -145,6 +145,12 @@ public interface KeycloakSession {
*/ */
UserFederatedStorageProvider userFederatedStorage(); UserFederatedStorageProvider userFederatedStorage();
/**
* Key manager
*
* @return
*/
KeyManager keys();
/** /**
* Keycloak scripting support. * Keycloak scripting support.

View file

@ -178,35 +178,6 @@ public interface RealmModel extends RoleContainerModel {
void setAccessCodeLifespanLogin(int seconds); void setAccessCodeLifespanLogin(int seconds);
String getKeyId();
String getPublicKeyPem();
void setPublicKeyPem(String publicKeyPem);
String getPrivateKeyPem();
void setPrivateKeyPem(String privateKeyPem);
PublicKey getPublicKey();
void setPublicKey(PublicKey publicKey);
String getCodeSecret();
Key getCodeSecretKey();
void setCodeSecret(String codeSecret);
X509Certificate getCertificate();
void setCertificate(X509Certificate certificate);
String getCertificatePem();
void setCertificatePem(String certificate);
PrivateKey getPrivateKey();
void setPrivateKey(PrivateKey privateKey);
List<RequiredCredentialModel> getRequiredCredentials(); List<RequiredCredentialModel> getRequiredCredentials();
void addRequiredCredential(String cred); void addRequiredCredential(String cred);

View file

@ -0,0 +1,66 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.utils;
import org.keycloak.component.ComponentFactory;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderFactory;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ComponentUtil {
public static Map<String, ProviderConfigProperty> getComponentConfigProperties(KeycloakSession session, ComponentModel component) {
try {
List<ProviderConfigProperty> l = getComponentFactory(session, component).getConfigProperties();
Map<String, ProviderConfigProperty> properties = new HashMap<>();
for (ProviderConfigProperty p : l) {
properties.put(p.getName(), p);
}
return properties;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static ComponentFactory getComponentFactory(KeycloakSession session, ComponentModel component) {
Class<? extends Provider> provider;
try {
provider = (Class<? extends Provider>) Class.forName(component.getProviderType());
} catch (ClassNotFoundException e) {
throw new RuntimeException("Invalid provider type '" + component.getProviderType() + "'");
}
ProviderFactory<? extends Provider> f = session.getKeycloakSessionFactory().getProviderFactory(provider, component.getProviderId());
if (f == null) {
throw new RuntimeException("No such provider '" + component.getProviderId() + "'");
}
ComponentFactory cf = (ComponentFactory) f;
return cf;
}
}

View file

@ -17,15 +17,9 @@
package org.keycloak.models.utils; package org.keycloak.models.utils;
import org.keycloak.common.util.Time;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.OTPPolicy; import org.keycloak.models.OTPPolicy;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.PasswordToken;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -33,29 +27,6 @@ import org.keycloak.representations.PasswordToken;
*/ */
public class CredentialValidation { public class CredentialValidation {
public static boolean validPasswordToken(RealmModel realm, UserModel user, String encodedPasswordToken) {
try {
JWSInput jws = new JWSInput(encodedPasswordToken);
if (!RSAProvider.verify(jws, realm.getPublicKey())) {
return false;
}
PasswordToken passwordToken = jws.readJsonContent(PasswordToken.class);
if (!passwordToken.getRealm().equals(realm.getName())) {
return false;
}
if (!passwordToken.getUser().equals(user.getId())) {
return false;
}
if (Time.currentTime() - passwordToken.getTimestamp() > realm.getAccessCodeLifespanUserAction()) {
return false;
}
return true;
} catch (JWSInputException e) {
return false;
}
}
public static boolean validOTP(RealmModel realm, String token, String secret) { public static boolean validOTP(RealmModel realm, String token, String secret) {
OTPPolicy policy = realm.getOTPPolicy(); OTPPolicy policy = realm.getOTPPolicy();
if (policy.getType().equals(UserCredentialModel.TOTP)) { if (policy.getType().equals(UserCredentialModel.TOTP)) {

View file

@ -0,0 +1,62 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.utils;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.keys.KeyProvider;
import org.keycloak.models.RealmModel;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class DefaultKeyProviders {
public static void createProviders(RealmModel realm) {
ComponentModel generated = new ComponentModel();
generated.setName("rsa-generated");
generated.setParentId(realm.getId());
generated.setProviderId("rsa-generated");
generated.setProviderType(KeyProvider.class.getName());
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
config.putSingle("priority", "100");
generated.setConfig(config);
realm.addComponentModel(generated);
}
public static void createProviders(RealmModel realm, String privateKeyPem, String certificatePem) {
ComponentModel rsa = new ComponentModel();
rsa.setName("rsa");
rsa.setParentId(realm.getId());
rsa.setProviderId("rsa");
rsa.setProviderType(KeyProvider.class.getName());
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
config.putSingle("priority", "100");
config.putSingle("privateKey", privateKeyPem);
if (certificatePem != null) {
config.putSingle("certificate", certificatePem);
}
rsa.setConfig(config);
realm.addComponentModel(rsa);
}
}

View file

@ -17,12 +17,15 @@
package org.keycloak.models.utils; package org.keycloak.models.utils;
import org.bouncycastle.openssl.PEMWriter;
import org.keycloak.broker.social.SocialIdentityProvider; import org.keycloak.broker.social.SocialIdentityProvider;
import org.keycloak.broker.social.SocialIdentityProviderFactory; import org.keycloak.broker.social.SocialIdentityProviderFactory;
import org.keycloak.common.util.Base64Url; import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.CertificateUtils; import org.keycloak.common.util.CertificateUtils;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.PemUtils; import org.keycloak.common.util.PemUtils;
import org.keycloak.component.ComponentModel;
import org.keycloak.keys.KeyProvider;
import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
@ -52,8 +55,6 @@ import javax.crypto.spec.SecretKeySpec;
import javax.transaction.InvalidTransactionException; import javax.transaction.InvalidTransactionException;
import javax.transaction.SystemException; import javax.transaction.SystemException;
import javax.transaction.Transaction; import javax.transaction.Transaction;
import java.io.IOException;
import java.io.StringWriter;
import java.security.Key; import java.security.Key;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.KeyPairGenerator; import java.security.KeyPairGenerator;
@ -135,82 +136,19 @@ public final class KeycloakModelUtils {
} }
public static String getPemFromKey(Key key) { public static String getPemFromKey(Key key) {
StringWriter writer = new StringWriter(); return PemUtils.encodeKey(key);
PEMWriter pemWriter = new PEMWriter(writer);
try {
pemWriter.writeObject(key);
pemWriter.flush();
pemWriter.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
String s = writer.toString();
return PemUtils.removeBeginEnd(s);
} }
public static String getPemFromCertificate(X509Certificate certificate) { public static String getPemFromCertificate(X509Certificate certificate) {
StringWriter writer = new StringWriter(); return PemUtils.encodeCertificate(certificate);
PEMWriter pemWriter = new PEMWriter(writer);
try {
pemWriter.writeObject(certificate);
pemWriter.flush();
pemWriter.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
String s = writer.toString();
return PemUtils.removeBeginEnd(s);
}
public static void generateRealmKeys(RealmModel realm) {
KeyPair keyPair = null;
try {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048);
keyPair = generator.generateKeyPair();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
realm.setPrivateKey(keyPair.getPrivate());
realm.setPublicKey(keyPair.getPublic());
X509Certificate certificate = null;
try {
certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName());
} catch (Exception e) {
throw new RuntimeException(e);
}
realm.setCertificate(certificate);
realm.setCodeSecret(generateCodeSecret());
}
public static void generateRealmCertificate(RealmModel realm) {
X509Certificate certificate = null;
try {
certificate = CertificateUtils.generateV1SelfSignedCertificate(new KeyPair(realm.getPublicKey(), realm.getPrivateKey()), realm.getName());
} catch (Exception e) {
throw new RuntimeException(e);
}
realm.setCertificate(certificate);
} }
public static CertificateRepresentation generateKeyPairCertificate(String subject) { public static CertificateRepresentation generateKeyPairCertificate(String subject) {
KeyPair keyPair = null; KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
try { X509Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, subject);
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048); String privateKeyPem = PemUtils.encodeKey(keyPair.getPrivate());
keyPair = generator.generateKeyPair(); String certPem = PemUtils.encodeCertificate(certificate);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
X509Certificate certificate = null;
try {
certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, subject);
} catch (Exception e) {
throw new RuntimeException(e);
}
String privateKeyPem = KeycloakModelUtils.getPemFromKey(keyPair.getPrivate());
String certPem = KeycloakModelUtils.getPemFromCertificate(certificate);
CertificateRepresentation rep = new CertificateRepresentation(); CertificateRepresentation rep = new CertificateRepresentation();
rep.setPrivateKey(privateKeyPem); rep.setPrivateKey(privateKeyPem);

View file

@ -25,6 +25,7 @@ import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.authorization.store.StoreFactory; import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialModel; import org.keycloak.credential.CredentialModel;
@ -265,16 +266,6 @@ public class ModelToRepresentation {
rep.setEnabled(realm.isEnabled()); rep.setEnabled(realm.isEnabled());
rep.setNotBefore(realm.getNotBefore()); rep.setNotBefore(realm.getNotBefore());
rep.setSslRequired(realm.getSslRequired().name().toLowerCase()); rep.setSslRequired(realm.getSslRequired().name().toLowerCase());
rep.setPublicKey(realm.getPublicKeyPem());
if (internal) {
rep.setPrivateKey(realm.getPrivateKeyPem());
String privateKeyPem = realm.getPrivateKeyPem();
if (realm.getCertificatePem() == null && privateKeyPem != null) {
KeycloakModelUtils.generateRealmCertificate(realm);
}
rep.setCodeSecret(realm.getCodeSecret());
}
rep.setCertificate(realm.getCertificatePem());
rep.setRegistrationAllowed(realm.isRegistrationAllowed()); rep.setRegistrationAllowed(realm.isRegistrationAllowed());
rep.setRegistrationEmailAsUsername(realm.isRegistrationEmailAsUsername()); rep.setRegistrationEmailAsUsername(realm.isRegistrationEmailAsUsername());
rep.setRememberMe(realm.isRememberMe()); rep.setRememberMe(realm.isRememberMe());
@ -783,19 +774,38 @@ public class ModelToRepresentation {
propRep.setType(prop.getType()); propRep.setType(prop.getType());
propRep.setDefaultValue(prop.getDefaultValue()); propRep.setDefaultValue(prop.getDefaultValue());
propRep.setHelpText(prop.getHelpText()); propRep.setHelpText(prop.getHelpText());
propRep.setSecret(prop.isSecret());
propertiesRep.add(propRep); propertiesRep.add(propRep);
} }
return propertiesRep; return propertiesRep;
} }
public static ComponentRepresentation toRepresentation(ComponentModel component) { public static ComponentRepresentation toRepresentation(KeycloakSession session, ComponentModel component, boolean internal) {
ComponentRepresentation rep = new ComponentRepresentation(); ComponentRepresentation rep = new ComponentRepresentation();
rep.setId(component.getId()); rep.setId(component.getId());
rep.setName(component.getName()); rep.setName(component.getName());
rep.setProviderId(component.getProviderId()); rep.setProviderId(component.getProviderId());
rep.setProviderType(component.getProviderType()); rep.setProviderType(component.getProviderType());
rep.setParentId(component.getParentId()); rep.setParentId(component.getParentId());
rep.setConfig(component.getConfig()); if (internal) {
rep.setConfig(component.getConfig());
} else {
Map<String, ProviderConfigProperty> configProperties = ComponentUtil.getComponentConfigProperties(session, component);
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
for (Map.Entry<String, List<String>> e : component.getConfig().entrySet()) {
ProviderConfigProperty configProperty = configProperties.get(e.getKey());
if (configProperty != null) {
if (configProperty.isSecret()) {
config.putSingle(e.getKey(), ComponentRepresentation.SECRET_VALUE);
} else {
config.put(e.getKey(), e.getValue());
}
}
}
rep.setConfig(config);
}
return rep; return rep;
} }

View file

@ -31,10 +31,14 @@ import org.keycloak.authorization.store.ScopeStore;
import org.keycloak.authorization.store.StoreFactory; import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.common.enums.SslRequired; import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.Base64; import org.keycloak.common.util.Base64;
import org.keycloak.common.util.CertificateUtils;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.PemUtils;
import org.keycloak.common.util.UriUtils; import org.keycloak.common.util.UriUtils;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialModel; import org.keycloak.credential.CredentialModel;
import org.keycloak.keys.KeyProvider;
import org.keycloak.migration.MigrationProvider; import org.keycloak.migration.MigrationProvider;
import org.keycloak.migration.migrators.MigrationUtils; import org.keycloak.migration.migrators.MigrationUtils;
import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.AuthenticationExecutionModel;
@ -63,6 +67,7 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationMapperModel; import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.idm.ApplicationRepresentation; import org.keycloak.representations.idm.ApplicationRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation; import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation; import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
@ -99,12 +104,16 @@ import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import java.io.IOException; import java.io.IOException;
import java.security.KeyPair;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.ListIterator;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
@ -183,23 +192,6 @@ public class RepresentationToModel {
if (rep.isVerifyEmail() != null) newRealm.setVerifyEmail(rep.isVerifyEmail()); if (rep.isVerifyEmail() != null) newRealm.setVerifyEmail(rep.isVerifyEmail());
if (rep.isResetPasswordAllowed() != null) newRealm.setResetPasswordAllowed(rep.isResetPasswordAllowed()); if (rep.isResetPasswordAllowed() != null) newRealm.setResetPasswordAllowed(rep.isResetPasswordAllowed());
if (rep.isEditUsernameAllowed() != null) newRealm.setEditUsernameAllowed(rep.isEditUsernameAllowed()); if (rep.isEditUsernameAllowed() != null) newRealm.setEditUsernameAllowed(rep.isEditUsernameAllowed());
if (rep.getPrivateKey() == null || rep.getPublicKey() == null) {
KeycloakModelUtils.generateRealmKeys(newRealm);
} else {
newRealm.setPrivateKeyPem(rep.getPrivateKey());
newRealm.setPublicKeyPem(rep.getPublicKey());
}
if (rep.getCertificate() == null) {
KeycloakModelUtils.generateRealmCertificate(newRealm);
} else {
newRealm.setCertificatePem(rep.getCertificate());
}
if (rep.getCodeSecret() == null) {
newRealm.setCodeSecret(KeycloakModelUtils.generateCodeSecret());
} else {
newRealm.setCodeSecret(rep.getCodeSecret());
}
if (rep.getLoginTheme() != null) newRealm.setLoginTheme(rep.getLoginTheme()); if (rep.getLoginTheme() != null) newRealm.setLoginTheme(rep.getLoginTheme());
if (rep.getAccountTheme() != null) newRealm.setAccountTheme(rep.getAccountTheme()); if (rep.getAccountTheme() != null) newRealm.setAccountTheme(rep.getAccountTheme());
if (rep.getAdminTheme() != null) newRealm.setAdminTheme(rep.getAdminTheme()); if (rep.getAdminTheme() != null) newRealm.setAdminTheme(rep.getAdminTheme());
@ -381,6 +373,13 @@ public class RepresentationToModel {
} }
} }
if (newRealm.getComponents(newRealm.getId(), KeyProvider.class.getName()).isEmpty()) {
if (rep.getPrivateKey() != null) {
DefaultKeyProviders.createProviders(newRealm, rep.getPrivateKey(), rep.getCertificate());
} else {
DefaultKeyProviders.createProviders(newRealm);
}
}
} }
protected static void importComponents(RealmModel newRealm, MultivaluedHashMap<String, ComponentExportRepresentation> components, String parentId) { protected static void importComponents(RealmModel newRealm, MultivaluedHashMap<String, ComponentExportRepresentation> components, String parentId) {
@ -819,20 +818,6 @@ public class RepresentationToModel {
realm.setUserFederationProviders(providerModels); realm.setUserFederationProviders(providerModels);
} }
if (Constants.GENERATE.equals(rep.getPublicKey())) {
KeycloakModelUtils.generateRealmKeys(realm);
} else {
if (rep.getPrivateKey() != null && rep.getPublicKey() != null) {
realm.setPrivateKeyPem(rep.getPrivateKey());
realm.setPublicKeyPem(rep.getPublicKey());
realm.setCodeSecret(KeycloakModelUtils.generateCodeSecret());
}
if (rep.getCertificate() != null) {
realm.setCertificatePem(rep.getCertificate());
}
}
if(rep.isInternationalizationEnabled() != null){ if(rep.isInternationalizationEnabled() != null){
realm.setInternationalizationEnabled(rep.isInternationalizationEnabled()); realm.setInternationalizationEnabled(rep.isInternationalizationEnabled());
} }
@ -1692,17 +1677,82 @@ public class RepresentationToModel {
return model; return model;
} }
public static ComponentModel toModel(KeycloakSession session, ComponentRepresentation rep) {
public static ComponentModel toModel(ComponentRepresentation rep) {
ComponentModel model = new ComponentModel(); ComponentModel model = new ComponentModel();
model.setParentId(rep.getParentId()); model.setParentId(rep.getParentId());
model.setProviderType(rep.getProviderType()); model.setProviderType(rep.getProviderType());
model.setProviderId(rep.getProviderId()); model.setProviderId(rep.getProviderId());
model.setConfig(rep.getConfig()); model.setConfig(new MultivaluedHashMap<>());
model.setName(rep.getName()); model.setName(rep.getName());
if (rep.getConfig() != null) {
Set<String> keys = new HashSet<>(rep.getConfig().keySet());
for (String k : keys) {
List<String> values = rep.getConfig().get(k);
if (values != null) {
ListIterator<String> itr = values.listIterator();
while (itr.hasNext()) {
String v = itr.next();
if (v == null || v.trim().isEmpty()) {
itr.remove();
}
}
if (!values.isEmpty()) {
model.getConfig().put(k, values);
}
}
}
}
return model; return model;
} }
public static void updateComponent(KeycloakSession session, ComponentRepresentation rep, ComponentModel component, boolean internal) {
if (rep.getParentId() != null) {
component.setParentId(rep.getParentId());
}
if (rep.getProviderType() != null) {
component.setProviderType(rep.getProviderType());
}
if (rep.getProviderId() != null) {
component.setProviderId(rep.getProviderId());
}
Map<String, ProviderConfigProperty> providerConfiguration = null;
if (!internal) {
providerConfiguration = ComponentUtil.getComponentConfigProperties(session, component);
}
if (rep.getConfig() != null) {
Set<String> keys = new HashSet<>(rep.getConfig().keySet());
for (String k : keys) {
if (!internal && !providerConfiguration.containsKey(k)) {
break;
}
List<String> values = rep.getConfig().get(k);
if (values == null || values.isEmpty() || values.get(0) == null || values.get(0).trim().isEmpty()) {
component.getConfig().remove(k);
} else {
ListIterator<String> itr = values.listIterator();
while (itr.hasNext()) {
String v = itr.next();
if (v == null || v.trim().isEmpty() || v.equals(ComponentRepresentation.SECRET_VALUE)) {
itr.remove();
}
}
if (!values.isEmpty()) {
component.getConfig().put(k, values);
}
}
}
}
}
public static void importAuthorizationSettings(ClientRepresentation clientRepresentation, ClientModel client, KeycloakSession session) { public static void importAuthorizationSettings(ClientRepresentation clientRepresentation, ClientModel client, KeycloakSession session) {
if (Boolean.TRUE.equals(clientRepresentation.getAuthorizationServicesEnabled())) { if (Boolean.TRUE.equals(clientRepresentation.getAuthorizationServicesEnabled())) {
AuthorizationProviderFactory authorizationFactory = (AuthorizationProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(AuthorizationProvider.class); AuthorizationProviderFactory authorizationFactory = (AuthorizationProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(AuthorizationProvider.class);

View file

@ -0,0 +1,121 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.provider;
import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ConfigurationValidationHelper {
private ComponentModel model;
private ConfigurationValidationHelper(ComponentModel model) {
this.model = model;
}
public static ConfigurationValidationHelper check(ComponentModel model) {
return new ConfigurationValidationHelper(model);
}
public ConfigurationValidationHelper checkInt(ProviderConfigProperty property, boolean required) throws ComponentValidationException {
return checkInt(property.getName(), property.getLabel(), required);
}
public ConfigurationValidationHelper checkInt(String key, String label, boolean required) throws ComponentValidationException {
checkSingle(key, label, required);
String val = model.getConfig().getFirst(key);
if (val != null) {
try {
Integer.parseInt(val);
} catch (NumberFormatException e) {
throw new ComponentValidationException(label + " should be a number");
}
}
return this;
}
public ConfigurationValidationHelper checkLong(ProviderConfigProperty property, boolean required) throws ComponentValidationException {
return checkLong(property.getName(), property.getLabel(), required);
}
public ConfigurationValidationHelper checkLong(String key, String label, boolean required) throws ComponentValidationException {
checkSingle(key, label, required);
String val = model.getConfig().getFirst(key);
if (val != null) {
try {
Long.parseLong(val);
} catch (NumberFormatException e) {
throw new ComponentValidationException(label + " should be a number");
}
}
return this;
}
public ConfigurationValidationHelper checkSingle(ProviderConfigProperty property, boolean required) throws ComponentValidationException {
return checkSingle(property.getName(), property.getLabel(), required);
}
public ConfigurationValidationHelper checkSingle(String key, String label, boolean required) throws ComponentValidationException {
if (model.getConfig().containsKey(key) && model.getConfig().get(key).size() > 1) {
throw new ComponentValidationException(label + " should be a single entry");
}
if (required) {
checkRequired(key, label);
}
return this;
}
public ConfigurationValidationHelper checkRequired(ProviderConfigProperty property) throws ComponentValidationException {
return checkRequired(property.getName(), property.getLabel());
}
public ConfigurationValidationHelper checkRequired(String key, String label) throws ComponentValidationException {
List<String> values = model.getConfig().get(key);
if (values == null) {
throw new ComponentValidationException(label + " is required");
}
return this;
}
public ConfigurationValidationHelper checkBoolean(ProviderConfigProperty property, boolean required) throws ComponentValidationException {
return checkBoolean(property.getName(), property.getLabel(), required);
}
public ConfigurationValidationHelper checkBoolean(String key, String label, boolean required) {
checkSingle(key, label, required);
String val = model.getConfig().getFirst(key);
if (val != null && !(val.equals("true") || val.equals("false"))) {
throw new ComponentValidationException(label + " should be 'true' or 'false'");
}
return this;
}
}

View file

@ -25,6 +25,7 @@ public class ProviderConfigProperty {
public static final String BOOLEAN_TYPE="boolean"; public static final String BOOLEAN_TYPE="boolean";
public static final String STRING_TYPE="String"; public static final String STRING_TYPE="String";
public static final String SCRIPT_TYPE="Script"; public static final String SCRIPT_TYPE="Script";
public static final String FILE_TYPE="File";
public static final String ROLE_TYPE="Role"; public static final String ROLE_TYPE="Role";
public static final String LIST_TYPE="List"; public static final String LIST_TYPE="List";
public static final String CLIENT_LIST_TYPE="ClientList"; public static final String CLIENT_LIST_TYPE="ClientList";
@ -35,6 +36,7 @@ public class ProviderConfigProperty {
protected String helpText; protected String helpText;
protected String type; protected String type;
protected Object defaultValue; protected Object defaultValue;
protected boolean secret;
public ProviderConfigProperty() { public ProviderConfigProperty() {
} }
@ -47,6 +49,11 @@ public class ProviderConfigProperty {
this.defaultValue = defaultValue; this.defaultValue = defaultValue;
} }
public ProviderConfigProperty(String name, String label, String helpText, String type, Object defaultValue, boolean secret) {
this(name, label, helpText, type, defaultValue);
this.secret = secret;
}
public String getName() { public String getName() {
return name; return name;
} }
@ -86,4 +93,13 @@ public class ProviderConfigProperty {
public void setHelpText(String helpText) { public void setHelpText(String helpText) {
this.helpText = helpText; this.helpText = helpText;
} }
public boolean isSecret() {
return secret;
}
public void setSecret(boolean secret) {
this.secret = secret;
}
} }

View file

@ -0,0 +1,114 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.provider;
import java.util.LinkedList;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ProviderConfigurationBuilder {
private List<ProviderConfigProperty> properties = new LinkedList<>();
private ProviderConfigurationBuilder() {
}
public static ProviderConfigurationBuilder create() {
return new ProviderConfigurationBuilder();
}
public ProviderConfigPropertyBuilder property() {
return new ProviderConfigPropertyBuilder();
}
public ProviderConfigurationBuilder property(ProviderConfigProperty property) {
properties.add(property);
return this;
}
public ProviderConfigurationBuilder property(String name, String label, String helpText, String type, Object defaultValue, boolean secret) {
ProviderConfigProperty property = new ProviderConfigProperty(name, label, helpText, type, defaultValue);
property.setSecret(secret);
properties.add(property);
return this;
}
public ProviderConfigurationBuilder property(String name, String label, String helpText, String type, Object defaultValue) {
properties.add(new ProviderConfigProperty(name, label, helpText, type, defaultValue));
return this;
}
public List<ProviderConfigProperty> build() {
return properties;
}
public class ProviderConfigPropertyBuilder {
private String name;
private String label;
private String helpText;
private String type;
private Object defaultValue;
private boolean secret;
public ProviderConfigPropertyBuilder name(String name) {
this.name = name;
return this;
}
public ProviderConfigPropertyBuilder label(String label) {
this.label = label;
return this;
}
public ProviderConfigPropertyBuilder helpText(String helpText) {
this.helpText = helpText;
return this;
}
public ProviderConfigPropertyBuilder type(String type) {
this.type = type;
return this;
}
public ProviderConfigPropertyBuilder defaultValue(Object defaultValue) {
this.defaultValue = defaultValue;
return this;
}
public ProviderConfigPropertyBuilder secret(boolean secret) {
this.secret = secret;
return this;
}
public ProviderConfigurationBuilder add() {
ProviderConfigProperty property = new ProviderConfigProperty();
property.setName(name);
property.setLabel(label);
property.setHelpText(helpText);
property.setType(type);
property.setDefaultValue(defaultValue);
property.setSecret(secret);
ProviderConfigurationBuilder.this.properties.add(property);
return ProviderConfigurationBuilder.this;
}
}
}

View file

@ -17,18 +17,23 @@
package org.keycloak.services.managers; package org.keycloak.services.managers;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Base64Url; import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.jose.jws.Algorithm;
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;
import org.keycloak.models.ClientTemplateModel; import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import javax.crypto.Mac; import java.security.PublicKey;
import java.security.Key; import java.security.Signature;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@ -38,8 +43,11 @@ import java.util.Set;
*/ */
public class ClientSessionCode { public class ClientSessionCode {
private static final byte[] HASH_SEPERATOR = "//".getBytes(); private static final Logger logger = Logger.getLogger(ClientSessionCode.class);
private static final String NEXT_CODE = ClientSessionCode.class.getName() + ".nextCode";
private KeycloakSession session;
private final RealmModel realm; private final RealmModel realm;
private final ClientSessionModel clientSession; private final ClientSessionModel clientSession;
@ -49,32 +57,12 @@ public class ClientSessionCode {
USER USER
} }
public ClientSessionCode(RealmModel realm, ClientSessionModel clientSession) { public ClientSessionCode(KeycloakSession session, RealmModel realm, ClientSessionModel clientSession) {
this.session = session;
this.realm = realm; this.realm = realm;
this.clientSession = clientSession; this.clientSession = clientSession;
} }
public static ClientSessionCode parse(String code, KeycloakSession session) {
try {
String[] parts = code.split("\\.");
String id = parts[1];
ClientSessionModel clientSession = session.sessions().getClientSession(id);
if (clientSession == null) {
return null;
}
String hash = createHash(clientSession.getRealm(), clientSession);
if (!hash.equals(parts[0])) {
return null;
}
return new ClientSessionCode(clientSession.getRealm(), clientSession);
} catch (RuntimeException e) {
return null;
}
}
public static class ParseResult { public static class ParseResult {
ClientSessionCode code; ClientSessionCode code;
boolean clientSessionNotFound; boolean clientSessionNotFound;
@ -114,13 +102,12 @@ public class ClientSessionCode {
return result; return result;
} }
String hash = createHash(realm, result.clientSession); if (!verifyCode(code, session, realm, result.clientSession)) {
if (!hash.equals(parts[0])) {
result.illegalHash = true; result.illegalHash = true;
return result; return result;
} }
result.code = new ClientSessionCode(realm, result.clientSession); result.code = new ClientSessionCode(session, realm, result.clientSession);
return result; return result;
} catch (RuntimeException e) { } catch (RuntimeException e) {
result.illegalHash = true; result.illegalHash = true;
@ -128,8 +115,6 @@ public class ClientSessionCode {
} }
} }
public static ClientSessionCode parse(String code, KeycloakSession session, RealmModel realm) { public static ClientSessionCode parse(String code, KeycloakSession session, RealmModel realm) {
try { try {
String[] parts = code.split("\\."); String[] parts = code.split("\\.");
@ -140,12 +125,11 @@ public class ClientSessionCode {
return null; return null;
} }
String hash = createHash(realm, clientSession); if (!verifyCode(code, session, realm, clientSession)) {
if (!hash.equals(parts[0])) {
return null; return null;
} }
return new ClientSessionCode(realm, clientSession); return new ClientSessionCode(session, realm, clientSession);
} catch (RuntimeException e) { } catch (RuntimeException e) {
return null; return null;
} }
@ -194,7 +178,7 @@ public class ClientSessionCode {
public Set<RoleModel> getRequestedRoles() { public Set<RoleModel> getRequestedRoles() {
Set<RoleModel> requestedRoles = new HashSet<RoleModel>(); Set<RoleModel> requestedRoles = new HashSet<>();
for (String roleId : clientSession.getRoles()) { for (String roleId : clientSession.getRoles()) {
RoleModel role = realm.getRoleById(roleId); RoleModel role = realm.getRoleById(roleId);
if (role != null) { if (role != null) {
@ -205,7 +189,7 @@ public class ClientSessionCode {
} }
public Set<ProtocolMapperModel> getRequestedProtocolMappers() { public Set<ProtocolMapperModel> getRequestedProtocolMappers() {
Set<ProtocolMapperModel> requestedProtocolMappers = new HashSet<ProtocolMapperModel>(); Set<ProtocolMapperModel> requestedProtocolMappers = new HashSet<>();
Set<String> protocolMappers = clientSession.getProtocolMappers(); Set<String> protocolMappers = clientSession.getProtocolMappers();
ClientModel client = clientSession.getClient(); ClientModel client = clientSession.getClient();
ClientTemplateModel template = client.getClientTemplate(); ClientTemplateModel template = client.getClientTemplate();
@ -229,32 +213,67 @@ public class ClientSessionCode {
} }
public String getCode() { public String getCode() {
return generateCode(realm, clientSession); String nextCode = (String) session.getAttribute(NEXT_CODE + "." + clientSession.getId());
if (nextCode == null) {
nextCode = generateCode(session, realm, clientSession);
session.setAttribute(NEXT_CODE + "." + clientSession.getId(), nextCode);
} else {
logger.debug("Code already generated for session, using code from session attributes");
}
return nextCode;
} }
private static String generateCode(RealmModel realm, ClientSessionModel clientSession) { private static String generateCode(KeycloakSession session, RealmModel realm, ClientSessionModel clientSession) {
String hash = createHash(realm, clientSession);
StringBuilder sb = new StringBuilder();
sb.append(hash);
sb.append(".");
sb.append(clientSession.getId());
return sb.toString();
}
private static String createHash(RealmModel realm, ClientSessionModel clientSession) {
try { try {
Key codeSecretKey = realm.getCodeSecretKey(); KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
Mac mac = Mac.getInstance(codeSecretKey.getAlgorithm());
mac.init(codeSecretKey); String secret = KeycloakModelUtils.generateSecret();
mac.update(clientSession.getId().getBytes());
mac.update(HASH_SEPERATOR); StringBuilder sb = new StringBuilder();
mac.update(clientSession.getNote(ClientSessionModel.ACTION_KEY).getBytes()); sb.append(secret);
return Base64Url.encode(mac.doFinal()); sb.append('.');
sb.append(clientSession.getId());
String code = sb.toString();
Signature signature = RSAProvider.getSignature(Algorithm.RS256);
signature.initSign(keys.getPrivateKey());
signature.update(code.getBytes("utf-8"));
sb = new StringBuilder();
sb.append(Base64Url.encode(signature.sign()));
sb.append('.');
sb.append(keys.getKid());
clientSession.setNote(ClientSessionModel.ACTION_SIGNATURE, sb.toString());
return code;
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
private static boolean verifyCode(String code, KeycloakSession session, RealmModel realm, ClientSessionModel clientSession) {
try {
String note = clientSession.getNote(ClientSessionModel.ACTION_SIGNATURE);
if (note == null) {
logger.debug("Action signature not found in client session");
return false;
}
clientSession.removeNote(ClientSessionModel.ACTION_SIGNATURE);
String[] signed = note.split("\\.");
PublicKey publicKey = session.keys().getPublicKey(realm, signed[1]);
Signature verifier = RSAProvider.getSignature(Algorithm.RS256);
verifier.initVerify(publicKey);
verifier.update(code.getBytes("utf-8"));
return verifier.verify(Base64Url.decode(signed[0]));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
} }

View file

@ -65,3 +65,4 @@ org.keycloak.transaction.TransactionManagerLookupSpi
org.keycloak.credential.hash.PasswordHashSpi org.keycloak.credential.hash.PasswordHashSpi
org.keycloak.credential.CredentialSpi org.keycloak.credential.CredentialSpi
org.keycloak.keys.PublicKeyStorageSpi org.keycloak.keys.PublicKeyStorageSpi
org.keycloak.keys.KeySpi

View file

@ -212,7 +212,7 @@ public class AuthenticationProcessor {
} }
public String generateCode() { public String generateCode() {
ClientSessionCode accessCode = new ClientSessionCode(getRealm(), getClientSession()); ClientSessionCode accessCode = new ClientSessionCode(session, getRealm(), getClientSession());
clientSession.setTimestamp(Time.currentTime()); clientSession.setTimestamp(Time.currentTime());
return accessCode.getCode(); return accessCode.getCode();
} }
@ -690,10 +690,10 @@ public class AuthenticationProcessor {
} }
public static Response redirectToRequiredActions(RealmModel realm, ClientSessionModel clientSession, UriInfo uriInfo) { public static Response redirectToRequiredActions(KeycloakSession session, RealmModel realm, ClientSessionModel clientSession, UriInfo uriInfo) {
// redirect to non-action url so browser refresh button works without reposting past data // redirect to non-action url so browser refresh button works without reposting past data
ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession); ClientSessionCode accessCode = new ClientSessionCode(session, realm, clientSession);
accessCode.setAction(ClientSessionModel.Action.REQUIRED_ACTIONS.name()); accessCode.setAction(ClientSessionModel.Action.REQUIRED_ACTIONS.name());
clientSession.setTimestamp(Time.currentTime()); clientSession.setTimestamp(Time.currentTime());
@ -764,7 +764,7 @@ public class AuthenticationProcessor {
} }
public void checkClientSession() { public void checkClientSession() {
ClientSessionCode code = new ClientSessionCode(realm, clientSession); ClientSessionCode code = new ClientSessionCode(session, realm, clientSession);
String action = ClientSessionModel.Action.AUTHENTICATE.name(); String action = ClientSessionModel.Action.AUTHENTICATE.name();
if (!code.isValidAction(action)) { if (!code.isValidAction(action)) {
throw new AuthenticationFlowException(AuthenticationFlowError.INVALID_CLIENT_SESSION); throw new AuthenticationFlowException(AuthenticationFlowError.INVALID_CLIENT_SESSION);
@ -862,7 +862,7 @@ public class AuthenticationProcessor {
protected Response authenticationComplete() { protected Response authenticationComplete() {
attachSession(); attachSession();
if (isActionRequired()) { if (isActionRequired()) {
return redirectToRequiredActions(realm, clientSession, uriInfo); return redirectToRequiredActions(session, realm, clientSession, uriInfo);
} else { } else {
event.detail(Details.CODE_ID, clientSession.getId()); // todo This should be set elsewhere. find out why tests fail. Don't know where this is supposed to be set event.detail(Details.CODE_ID, clientSession.getId()); // todo This should be set elsewhere. find out why tests fail. Don't know where this is supposed to be set
return AuthenticationManager.finishedRequiredActions(session, userSession, clientSession, connection, request, uriInfo, event); return AuthenticationManager.finishedRequiredActions(session, userSession, clientSession, connection, request, uriInfo, event);

View file

@ -148,7 +148,7 @@ public class RequiredActionContextResult implements RequiredActionContext {
@Override @Override
public String generateCode() { public String generateCode() {
ClientSessionCode accessCode = new ClientSessionCode(getRealm(), getClientSession()); ClientSessionCode accessCode = new ClientSessionCode(session, getRealm(), getClientSession());
clientSession.setTimestamp(Time.currentTime()); clientSession.setTimestamp(Time.currentTime());
return accessCode.getCode(); return accessCode.getCode();
} }

View file

@ -256,7 +256,6 @@ public class SerializedBrokeredIdentityContext implements UpdateProfileContext {
ctx.setLastName(getLastName()); ctx.setLastName(getLastName());
ctx.setBrokerSessionId(getBrokerSessionId()); ctx.setBrokerSessionId(getBrokerSessionId());
ctx.setBrokerUserId(getBrokerUserId()); ctx.setBrokerUserId(getBrokerUserId());
ctx.setCode(getCode());
ctx.setToken(getToken()); ctx.setToken(getToken());
RealmModel realm = clientSession.getRealm(); RealmModel realm = clientSession.getRealm();
@ -297,7 +296,6 @@ public class SerializedBrokeredIdentityContext implements UpdateProfileContext {
ctx.setLastName(context.getLastName()); ctx.setLastName(context.getLastName());
ctx.setBrokerSessionId(context.getBrokerSessionId()); ctx.setBrokerSessionId(context.getBrokerSessionId());
ctx.setBrokerUserId(context.getBrokerUserId()); ctx.setBrokerUserId(context.getBrokerUserId());
ctx.setCode(context.getCode());
ctx.setToken(context.getToken()); ctx.setToken(context.getToken());
ctx.setIdentityProviderId(context.getIdpConfig().getAlias()); ctx.setIdentityProviderId(context.getIdpConfig().getAlias());

View file

@ -63,7 +63,7 @@ public class IdentityProviderAuthenticator implements Authenticator {
List<IdentityProviderModel> identityProviders = context.getRealm().getIdentityProviders(); List<IdentityProviderModel> identityProviders = context.getRealm().getIdentityProviders();
for (IdentityProviderModel identityProvider : identityProviders) { for (IdentityProviderModel identityProvider : identityProviders) {
if (identityProvider.isEnabled() && providerId.equals(identityProvider.getAlias())) { if (identityProvider.isEnabled() && providerId.equals(identityProvider.getAlias())) {
String accessCode = new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode(); String accessCode = new ClientSessionCode(context.getSession(), context.getRealm(), context.getClientSession()).getCode();
Response response = Response.seeOther( Response response = Response.seeOther(
Urls.identityProviderAuthnRequest(context.getUriInfo().getBaseUri(), providerId, context.getRealm().getName(), accessCode)) Urls.identityProviderAuthnRequest(context.getUriInfo().getBaseUri(), providerId, context.getRealm().getName(), accessCode))
.build(); .build();

View file

@ -38,6 +38,7 @@ import org.keycloak.authorization.util.Permissions;
import org.keycloak.authorization.util.Tokens; import org.keycloak.authorization.util.Tokens;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException; import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
@ -77,6 +78,9 @@ public class AuthorizationTokenService {
@Context @Context
private HttpRequest httpRequest; private HttpRequest httpRequest;
@Context
private KeycloakSession session;
public AuthorizationTokenService(AuthorizationProvider authorization) { public AuthorizationTokenService(AuthorizationProvider authorization) {
this.authorization = authorization; this.authorization = authorization;
} }
@ -180,7 +184,7 @@ public class AuthorizationTokenService {
String rpt = request.getRpt(); String rpt = request.getRpt();
if (rpt != null && !"".equals(rpt)) { if (rpt != null && !"".equals(rpt)) {
if (!Tokens.verifySignature(rpt, getRealm().getPublicKey())) { if (!Tokens.verifySignature(session, getRealm(), rpt)) {
throw new ErrorResponseException("invalid_rpt", "RPT signature is invalid", Status.FORBIDDEN); throw new ErrorResponseException("invalid_rpt", "RPT signature is invalid", Status.FORBIDDEN);
} }
@ -252,13 +256,13 @@ public class AuthorizationTokenService {
authorization.setPermissions(permissions); authorization.setPermissions(permissions);
accessToken.setAuthorization(authorization); accessToken.setAuthorization(authorization);
return new TokenManager().encodeToken(getRealm(), accessToken); return new TokenManager().encodeToken(session, getRealm(), accessToken);
} }
private PermissionTicket verifyPermissionTicket(AuthorizationRequest request) { private PermissionTicket verifyPermissionTicket(AuthorizationRequest request) {
String ticketString = request.getTicket(); String ticketString = request.getTicket();
if (ticketString == null || !Tokens.verifySignature(ticketString, getRealm().getPublicKey())) { if (ticketString == null || !Tokens.verifySignature(session, getRealm(), ticketString)) {
throw new ErrorResponseException("invalid_ticket", "Ticket verification failed", Status.FORBIDDEN); throw new ErrorResponseException("invalid_ticket", "Ticket verification failed", Status.FORBIDDEN);
} }

View file

@ -17,6 +17,7 @@
*/ */
package org.keycloak.authorization.config; package org.keycloak.authorization.config;
import org.keycloak.common.util.PemUtils;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
@ -46,7 +47,7 @@ public class UmaWellKnownProvider implements WellKnownProvider {
return Configuration.fromDefault(RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString(), realm.getName(), return Configuration.fromDefault(RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString(), realm.getName(),
URI.create(RealmsResource.protocolUrl(uriInfo).path(OIDCLoginProtocolService.class, "auth").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString()), URI.create(RealmsResource.protocolUrl(uriInfo).path(OIDCLoginProtocolService.class, "auth").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString()),
URI.create(RealmsResource.protocolUrl(uriInfo).path(OIDCLoginProtocolService.class, "token").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString()), URI.create(RealmsResource.protocolUrl(uriInfo).path(OIDCLoginProtocolService.class, "token").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString()),
realm.getPublicKeyPem()); PemUtils.encodeKey(session.keys().getActiveKey(realm).getPublicKey()));
} }
@Override @Override

View file

@ -40,6 +40,7 @@ import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException; import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakContext; import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
@ -81,6 +82,9 @@ public class EntitlementService {
@Context @Context
private HttpRequest request; private HttpRequest request;
@Context
private KeycloakSession session;
public EntitlementService(AuthorizationProvider authorization) { public EntitlementService(AuthorizationProvider authorization) {
this.authorization = authorization; this.authorization = authorization;
} }
@ -200,7 +204,7 @@ public class EntitlementService {
authorization.setPermissions(permissions); authorization.setPermissions(permissions);
accessToken.setAuthorization(authorization); accessToken.setAuthorization(authorization);
return new TokenManager().encodeToken(realm, accessToken); return new TokenManager().encodeToken(this.authorization.getKeycloakSession(), realm, accessToken);
} }
private List<ResourcePermission> createPermissions(EntitlementRequest entitlementRequest, ResourceServer resourceServer, AuthorizationProvider authorization) { private List<ResourcePermission> createPermissions(EntitlementRequest entitlementRequest, ResourceServer resourceServer, AuthorizationProvider authorization) {
@ -252,7 +256,7 @@ public class EntitlementService {
if (rpt != null && !"".equals(rpt)) { if (rpt != null && !"".equals(rpt)) {
KeycloakContext context = authorization.getKeycloakSession().getContext(); KeycloakContext context = authorization.getKeycloakSession().getContext();
if (!Tokens.verifySignature(rpt, context.getRealm().getPublicKey())) { if (!Tokens.verifySignature(session, context.getRealm(), rpt)) {
throw new ErrorResponseException("invalid_rpt", "RPT signature is invalid", Status.FORBIDDEN); throw new ErrorResponseException("invalid_rpt", "RPT signature is invalid", Status.FORBIDDEN);
} }

View file

@ -25,6 +25,7 @@ import org.keycloak.authorization.protection.permission.representation.Permissio
import org.keycloak.authorization.protection.permission.representation.PermissionResponse; import org.keycloak.authorization.protection.permission.representation.PermissionResponse;
import org.keycloak.authorization.store.StoreFactory; import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.models.KeyManager;
import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.services.ErrorResponseException; import org.keycloak.services.ErrorResponseException;
@ -130,7 +131,8 @@ public class AbstractPermissionService {
} }
private String createPermissionTicket(List<ResourceRepresentation> resources) { private String createPermissionTicket(List<ResourceRepresentation> resources) {
return new JWSBuilder().jsonContent(new PermissionTicket(resources, this.resourceServer.getId(), this.identity.getAccessToken())) KeyManager.ActiveKey keys = this.authorization.getKeycloakSession().keys().getActiveKey(this.authorization.getRealm());
.rsa256(this.authorization.getKeycloakSession().getContext().getRealm().getPrivateKey()); return new JWSBuilder().kid(keys.getKid()).jsonContent(new PermissionTicket(resources, this.resourceServer.getId(), this.identity.getAccessToken()))
.rsa256(keys.getPrivateKey());
} }
} }

View file

@ -22,6 +22,7 @@ import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider; import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.KeycloakContext; import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.services.ErrorResponseException; import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.managers.AppAuthManager; import org.keycloak.services.managers.AppAuthManager;
@ -53,10 +54,10 @@ public class Tokens {
return authManager.extractAuthorizationHeaderToken(keycloakSession.getContext().getRequestHeaders()); return authManager.extractAuthorizationHeaderToken(keycloakSession.getContext().getRequestHeaders());
} }
public static boolean verifySignature(String token, PublicKey publicKey) { public static boolean verifySignature(KeycloakSession keycloakSession, RealmModel realm, String token) {
try { try {
JWSInput jws = new JWSInput(token); JWSInput jws = new JWSInput(token);
PublicKey publicKey = keycloakSession.keys().getPublicKey(realm, jws.getHeader().getKeyId());
return RSAProvider.verify(jws, publicKey); return RSAProvider.verify(jws, publicKey);
} catch (Exception e) { } catch (Exception e) {
throw new ErrorResponseException("invalid_signature", "Unexpected error while validating signature.", Status.INTERNAL_SERVER_ERROR); throw new ErrorResponseException("invalid_signature", "Unexpected error while validating signature.", Status.INTERNAL_SERVER_ERROR);

View file

@ -233,9 +233,9 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
federatedIdentity.setToken(response); federatedIdentity.setToken(response);
} }
federatedIdentity.setCode(state);
federatedIdentity.setIdpConfig(getConfig()); federatedIdentity.setIdpConfig(getConfig());
federatedIdentity.setIdp(AbstractOAuth2IdentityProvider.this); federatedIdentity.setIdp(AbstractOAuth2IdentityProvider.this);
federatedIdentity.setCode(state);
return callback.authenticated(federatedIdentity); return callback.authenticated(federatedIdentity);
} }

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.EventBuilder; import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
@ -265,7 +266,8 @@ public class SAMLEndpoint {
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder() JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
.relayState(relayState); .relayState(relayState);
if (config.isWantAuthnRequestsSigned()) { if (config.isWantAuthnRequestsSigned()) {
binding.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()) KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
binding.signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate())
.signatureAlgorithm(provider.getSignatureAlgorithm()) .signatureAlgorithm(provider.getSignatureAlgorithm())
.signDocument(); .signDocument();
} }
@ -291,13 +293,13 @@ public class SAMLEndpoint {
protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState) { protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState) {
try { try {
AssertionType assertion = AssertionUtil.getAssertion(responseType, realm.getPrivateKey()); KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
AssertionType assertion = AssertionUtil.getAssertion(responseType, keys.getPrivateKey());
SubjectType subject = assertion.getSubject(); SubjectType subject = assertion.getSubject();
SubjectType.STSubType subType = subject.getSubType(); SubjectType.STSubType subType = subject.getSubType();
NameIDType subjectNameID = (NameIDType) subType.getBaseID(); NameIDType subjectNameID = (NameIDType) subType.getBaseID();
//Map<String, String> notes = new HashMap<>(); //Map<String, String> notes = new HashMap<>();
BrokeredIdentityContext identity = new BrokeredIdentityContext(subjectNameID.getValue()); BrokeredIdentityContext identity = new BrokeredIdentityContext(subjectNameID.getValue());
identity.setCode(relayState);
identity.getContextData().put(SAML_LOGIN_RESPONSE, responseType); identity.getContextData().put(SAML_LOGIN_RESPONSE, responseType);
identity.getContextData().put(SAML_ASSERTION, assertion); identity.getContextData().put(SAML_ASSERTION, assertion);
@ -340,6 +342,7 @@ public class SAMLEndpoint {
if (authn != null && authn.getSessionIndex() != null) { if (authn != null && authn.getSessionIndex() != null) {
identity.setBrokerSessionId(identity.getBrokerUserId() + "." + authn.getSessionIndex()); identity.setBrokerSessionId(identity.getBrokerUserId() + "." + authn.getSessionIndex());
} }
identity.setCode(relayState);
return callback.authenticated(identity); return callback.authenticated(identity);

View file

@ -23,6 +23,7 @@ import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.IdentityBrokerException; import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.provider.IdentityProviderDataMarshaller; import org.keycloak.broker.provider.IdentityProviderDataMarshaller;
import org.keycloak.broker.provider.util.SimpleHttp; import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.common.util.PemUtils;
import org.keycloak.dom.saml.v2.assertion.AssertionType; import org.keycloak.dom.saml.v2.assertion.AssertionType;
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType; import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
import org.keycloak.dom.saml.v2.assertion.NameIDType; import org.keycloak.dom.saml.v2.assertion.NameIDType;
@ -31,6 +32,7 @@ import org.keycloak.dom.saml.v2.protocol.ResponseType;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
@ -97,18 +99,9 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
.relayState(request.getState()); .relayState(request.getState());
if (getConfig().isWantAuthnRequestsSigned()) { if (getConfig().isWantAuthnRequestsSigned()) {
PrivateKey privateKey = realm.getPrivateKey(); KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
PublicKey publicKey = realm.getPublicKey();
if (privateKey == null) { KeyPair keypair = new KeyPair(keys.getPublicKey(), keys.getPrivateKey());
throw new IdentityBrokerException("Identity Provider [" + getConfig().getAlias() + "] wants a signed authentication request. But the Realm [" + realm.getName() + "] does not have a private key.");
}
if (publicKey == null) {
throw new IdentityBrokerException("Identity Provider [" + getConfig().getAlias() + "] wants a signed authentication request. But the Realm [" + realm.getName() + "] does not have a public key.");
}
KeyPair keypair = new KeyPair(publicKey, privateKey);
binding.signWith(keypair); binding.signWith(keypair);
binding.signatureAlgorithm(getSignatureAlgorithm()); binding.signatureAlgorithm(getSignatureAlgorithm());
@ -155,7 +148,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
String singleLogoutServiceUrl = getConfig().getSingleLogoutServiceUrl(); String singleLogoutServiceUrl = getConfig().getSingleLogoutServiceUrl();
if (singleLogoutServiceUrl == null || singleLogoutServiceUrl.trim().equals("") || !getConfig().isBackchannelSupported()) return; if (singleLogoutServiceUrl == null || singleLogoutServiceUrl.trim().equals("") || !getConfig().isBackchannelSupported()) return;
SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl); SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
JaxrsSAML2BindingBuilder binding = buildLogoutBinding(userSession, realm); JaxrsSAML2BindingBuilder binding = buildLogoutBinding(session, userSession, realm);
try { try {
int status = SimpleHttp.doPost(singleLogoutServiceUrl) int status = SimpleHttp.doPost(singleLogoutServiceUrl)
.param(GeneralConstants.SAML_REQUEST_KEY, binding.postBinding(logoutBuilder.buildDocument()).encoded()) .param(GeneralConstants.SAML_REQUEST_KEY, binding.postBinding(logoutBuilder.buildDocument()).encoded())
@ -181,7 +174,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
} else { } else {
try { try {
SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl); SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
JaxrsSAML2BindingBuilder binding = buildLogoutBinding(userSession, realm); JaxrsSAML2BindingBuilder binding = buildLogoutBinding(session, userSession, realm);
return binding.postBinding(logoutBuilder.buildDocument()).request(singleLogoutServiceUrl); return binding.postBinding(logoutBuilder.buildDocument()).request(singleLogoutServiceUrl);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@ -200,11 +193,12 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
return logoutBuilder; return logoutBuilder;
} }
private JaxrsSAML2BindingBuilder buildLogoutBinding(UserSessionModel userSession, RealmModel realm) { private JaxrsSAML2BindingBuilder buildLogoutBinding(KeycloakSession session, UserSessionModel userSession, RealmModel realm) {
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder() JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
.relayState(userSession.getId()); .relayState(userSession.getId());
if (getConfig().isWantAuthnRequestsSigned()) { if (getConfig().isWantAuthnRequestsSigned()) {
binding.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()) KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
binding.signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate())
.signatureAlgorithm(getSignatureAlgorithm()) .signatureAlgorithm(getSignatureAlgorithm())
.signDocument(); .signDocument();
} }
@ -231,7 +225,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
boolean wantAuthnRequestsSigned = getConfig().isWantAuthnRequestsSigned(); boolean wantAuthnRequestsSigned = getConfig().isWantAuthnRequestsSigned();
String entityId = getEntityId(uriInfo, realm); String entityId = getEntityId(uriInfo, realm);
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat(); String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
String certificatePem = realm.getCertificatePem(); String certificatePem = PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate());
String descriptor = SPMetadataDescriptor.getSPDescriptor(authnBinding, endpoint, endpoint, wantAuthnRequestsSigned, entityId, nameIDPolicyFormat, certificatePem); String descriptor = SPMetadataDescriptor.getSPDescriptor(authnBinding, endpoint, endpoint, wantAuthnRequestsSigned, entityId, nameIDPolicyFormat, certificatePem);
return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build(); return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
} }

View file

@ -0,0 +1,141 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.keys;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.RealmModel;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public abstract class AbstractRsaKeyProvider implements KeyProvider {
private final boolean enabled;
private final boolean active;
private final ComponentModel model;
private final Keys keys;
public AbstractRsaKeyProvider(RealmModel realm, ComponentModel model) {
this.model = model;
this.enabled = model.get(Attributes.ENABLED_KEY, true);
this.active = model.get(Attributes.ACTIVE_KEY, true);
if (model.hasNote(Keys.class.getName())) {
keys = model.getNote(Keys.class.getName());
} else {
keys = loadKeys(realm, model);
model.setNote(Keys.class.getName(), keys);
}
}
protected abstract Keys loadKeys(RealmModel realm, ComponentModel model);
@Override
public final String getKid() {
return isActive() ? keys.getKid() : null;
}
@Override
public final PrivateKey getPrivateKey() {
return isActive() ? keys.getKeyPair().getPrivate() : null;
}
@Override
public final PublicKey getPublicKey(String kid) {
return isEnabled() && kid.equals(keys.getKid()) ? keys.getKeyPair().getPublic() : null;
}
@Override
public X509Certificate getCertificate(String kid) {
return isEnabled() && kid.equals(keys.getKid()) ? keys.getCertificate() : null;
}
@Override
public final List<KeyMetadata> getKeyMetadata() {
String kid = keys.getKid();
PublicKey publicKey = keys.getKeyPair().getPublic();
if (kid != null && publicKey != null) {
KeyMetadata k = new KeyMetadata();
k.setProviderId(model.getId());
k.setProviderPriority(model.get(Attributes.PRIORITY_KEY, 0l));
k.setKid(kid);
if (isActive()) {
k.setStatus(KeyMetadata.Status.ACTIVE);
} else if (isEnabled()) {
k.setStatus(KeyMetadata.Status.PASSIVE);
} else {
k.setStatus(KeyMetadata.Status.DISABLED);
}
k.setType(KeyMetadata.Type.RSA);
k.setPublicKey(publicKey);
k.setCertificate(keys.getCertificate());
return Collections.singletonList(k);
} else {
return Collections.emptyList();
}
}
@Override
public void close() {
}
private boolean isEnabled() {
return keys != null && enabled;
}
private boolean isActive() {
return isEnabled() && active;
}
public static class Keys {
private String kid;
private KeyPair keyPair;
private X509Certificate certificate;
public Keys(String kid, KeyPair keyPair, X509Certificate certificate) {
this.kid = kid;
this.keyPair = keyPair;
this.certificate = certificate;
}
public String getKid() {
return kid;
}
public KeyPair getKeyPair() {
return keyPair;
}
public X509Certificate getCertificate() {
return certificate;
}
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.keys;
import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.ConfigurationValidationHelper;
import org.keycloak.provider.ProviderConfigurationBuilder;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public abstract class AbstractRsaKeyProviderFactory implements KeyProviderFactory {
public final static ProviderConfigurationBuilder configurationBuilder() {
return ProviderConfigurationBuilder.create()
.property(Attributes.PRIORITY_PROPERTY)
.property(Attributes.ENABLED_PROPERTY)
.property(Attributes.ACTIVE_PROPERTY);
}
@Override
public void validateConfiguration(KeycloakSession session, ComponentModel model) throws ComponentValidationException {
ConfigurationValidationHelper.check(model)
.checkLong(Attributes.PRIORITY_PROPERTY, false)
.checkBoolean(Attributes.ENABLED_PROPERTY, false)
.checkBoolean(Attributes.ACTIVE_PROPERTY, false);
}
}

View file

@ -0,0 +1,49 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.keys;
import org.keycloak.provider.ProviderConfigProperty;
import static org.keycloak.provider.ProviderConfigProperty.BOOLEAN_TYPE;
import static org.keycloak.provider.ProviderConfigProperty.FILE_TYPE;
import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface Attributes {
String PRIORITY_KEY = "priority";
ProviderConfigProperty PRIORITY_PROPERTY = new ProviderConfigProperty(PRIORITY_KEY, "Priority", "Priority for the provider", STRING_TYPE, "0");
String ENABLED_KEY = "enabled";
ProviderConfigProperty ENABLED_PROPERTY = new ProviderConfigProperty(ENABLED_KEY, "Enabled", "Set if the keys are enabled", BOOLEAN_TYPE, "true");
String ACTIVE_KEY = "active";
ProviderConfigProperty ACTIVE_PROPERTY = new ProviderConfigProperty(ACTIVE_KEY, "Active", "Set if the keys can be used for signing", BOOLEAN_TYPE, "true");
String PRIVATE_KEY_KEY = "privateKey";
ProviderConfigProperty PRIVATE_KEY_PROPERTY = new ProviderConfigProperty(PRIVATE_KEY_KEY, "Private RSA Key", "Private RSA Key encoded in PEM format", FILE_TYPE, null, true);
String CERTIFICATE_KEY = "certificate";
ProviderConfigProperty CERTIFICATE_PROPERTY = new ProviderConfigProperty(CERTIFICATE_KEY, "X509 Certificate", "X509 Certificate encoded in PEM format", FILE_TYPE, null);
String KEY_SIZE_KEY = "keySize";
ProviderConfigProperty KEY_SIZE_PROPERTY = new ProviderConfigProperty(KEY_SIZE_KEY, "Keysize", "Size for the generated keys (1024, 2048 or 4096)", STRING_TYPE, null);
}

View file

@ -0,0 +1,162 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.keys;
import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ProviderFactory;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class DefaultKeyManager implements KeyManager {
private static final Logger logger = Logger.getLogger(DefaultKeyManager.class);
private final KeycloakSession session;
private final Map<String, List<KeyProvider>> providersMap = new HashMap<>();
public DefaultKeyManager(KeycloakSession session) {
this.session = session;
}
@Override
public ActiveKey getActiveKey(RealmModel realm) {
for (KeyProvider p : getProviders(realm)) {
if (p.getKid() != null && p.getPrivateKey() != null) {
if (logger.isTraceEnabled()) {
logger.tracev("Active key realm={0} kid={1}", realm.getName(), p.getKid());
}
String kid = p.getKid();
return new ActiveKey(kid, p.getPrivateKey(), p.getPublicKey(kid), p.getCertificate(kid));
}
}
throw new RuntimeException("Failed to get keys");
}
@Override
public PublicKey getPublicKey(RealmModel realm, String kid) {
if (kid == null) {
logger.warnv("KID is null, can't find public key", realm.getName(), kid);
return null;
}
for (KeyProvider p : getProviders(realm)) {
PublicKey publicKey = p.getPublicKey(kid);
if (publicKey != null) {
if (logger.isTraceEnabled()) {
logger.tracev("Found public key realm={0} kid={1}", realm.getName(), kid);
}
return publicKey;
}
}
if (logger.isTraceEnabled()) {
logger.tracev("Failed to find public key realm={0} kid={1}", realm.getName(), kid);
}
return null;
}
@Override
public Certificate getCertificate(RealmModel realm, String kid) {
if (kid == null) {
logger.warnv("KID is null, can't find public key", realm.getName(), kid);
return null;
}
for (KeyProvider p : getProviders(realm)) {
Certificate certificate = p.getCertificate(kid);
if (certificate != null) {
if (logger.isTraceEnabled()) {
logger.tracev("Found certificate realm={0} kid={1}", realm.getName(), kid);
}
return certificate;
}
}
if (logger.isTraceEnabled()) {
logger.tracev("Failed to find certificate realm={0} kid={1}", realm.getName(), kid);
}
return null;
}
@Override
public List<KeyMetadata> getKeys(RealmModel realm, boolean includeDisabled) {
List<KeyMetadata> keys = new LinkedList<>();
for (KeyProvider p : getProviders(realm)) {
if (includeDisabled) {
keys.addAll(p.getKeyMetadata());
} else {
p.getKeyMetadata().stream().filter(k -> k.getStatus() != KeyMetadata.Status.DISABLED).forEach(k -> keys.add(k));
}
}
return keys;
}
private List<KeyProvider> getProviders(RealmModel realm) {
boolean active = false;
List<KeyProvider> providers = providersMap.get(realm.getId());
if (providers == null) {
providers = new LinkedList<>();
List<ComponentModel> components = new LinkedList<>(realm.getComponents(realm.getId(), KeyProvider.class.getName()));
components.sort(new ProviderComparator());
for (ComponentModel c : components) {
try {
ProviderFactory<KeyProvider> f = session.getKeycloakSessionFactory().getProviderFactory(KeyProvider.class, c.getProviderId());
KeyProviderFactory factory = (KeyProviderFactory) f;
KeyProvider provider = factory.create(session, c);
session.enlistForClose(provider);
providers.add(provider);
if (!active && provider.getKid() != null && provider.getPrivateKey() != null) {
active = true;
}
} catch (Throwable t) {
logger.errorv(t, "Failed to load provider {0}", c.getId());
}
}
if (!active) {
providers.add(new FailsafeRsaKeyProvider());
}
providersMap.put(realm.getId(), providers);
}
return providers;
}
private class ProviderComparator implements Comparator<ComponentModel> {
@Override
public int compare(ComponentModel o1, ComponentModel o2) {
int i = Long.compare(o2.get("priority", 0l), o1.get("priority", 0l));
return i != 0 ? i : o1.getId().compareTo(o2.getId());
}
}
}

View file

@ -0,0 +1,96 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.keys;
import org.jboss.logging.Logger;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.Time;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class FailsafeRsaKeyProvider implements KeyProvider {
private static final Logger logger = Logger.getLogger(FailsafeRsaKeyProvider.class);
private static String KID;
private static KeyPair KEY_PAIR;
private static long EXPIRES;
private KeyPair keyPair;
private String kid;
public FailsafeRsaKeyProvider() {
logger.errorv("No active keys found, using failsafe provider, please login to admin console to add keys. Clustering is not supported.");
synchronized (FailsafeRsaKeyProvider.class) {
if (EXPIRES < Time.currentTime()) {
KEY_PAIR = KeyUtils.generateRsaKeyPair(2048);
KID = KeyUtils.createKeyId(KEY_PAIR.getPublic());
EXPIRES = Time.currentTime() + 60 * 10;
if (EXPIRES > 0) {
logger.warnv("Keys expired, re-generated kid={0}", KID);
}
}
kid = KID;
keyPair = KEY_PAIR;
}
}
@Override
public String getKid() {
return kid;
}
@Override
public PrivateKey getPrivateKey() {
return keyPair.getPrivate();
}
@Override
public PublicKey getPublicKey(String kid) {
return kid.equals(this.kid) ? keyPair.getPublic() : null;
}
@Override
public X509Certificate getCertificate(String kid) {
return null;
}
@Override
public List<KeyMetadata> getKeyMetadata() {
return Collections.emptyList();
}
@Override
public void close() {
}
}

View file

@ -0,0 +1,142 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.keys;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.common.util.CertificateUtils;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.PemUtils;
import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ConfigurationValidationHelper;
import org.keycloak.provider.ProviderConfigProperty;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class GeneratedRsaKeyProviderFactory extends AbstractRsaKeyProviderFactory {
private static final Logger logger = Logger.getLogger(GeneratedRsaKeyProviderFactory.class);
public static final String ID = "rsa-generated";
private static final String HELP_TEXT = "Generates RSA keys and creates a self-signed certificate";
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = AbstractRsaKeyProviderFactory.configurationBuilder()
.property(Attributes.KEY_SIZE_PROPERTY)
.build();
@Override
public KeyProvider create(KeycloakSession session, ComponentModel model) {
return new RsaKeyProvider(session.getContext().getRealm(), model);
}
@Override
public void validateConfiguration(KeycloakSession session, ComponentModel model) throws ComponentValidationException {
super.validateConfiguration(session, model);
ConfigurationValidationHelper.check(model)
.checkInt(Attributes.KEY_SIZE_PROPERTY, false);
int size;
if (!model.contains(Attributes.KEY_SIZE_KEY)) {
size = 2048;
model.put(Attributes.KEY_SIZE_KEY, size);
} else {
size = model.get(Attributes.KEY_SIZE_KEY, 2048);
if (size != 1024 && size != 2048 && size != 4096) {
throw new ComponentValidationException("Keysize should be 1024, 2048 or 4096");
}
}
if (!(model.contains(Attributes.PRIVATE_KEY_KEY) && model.contains(Attributes.CERTIFICATE_KEY))) {
RealmModel realm = session.realms().getRealm(model.getParentId());
generateKeys(realm, model, size);
logger.debugv("Generated keys for {0}", realm.getName());
} else {
PrivateKey privateKey = PemUtils.decodePrivateKey(model.get(Attributes.PRIVATE_KEY_KEY));
int currentSize = ((RSAPrivateKey) privateKey).getModulus().bitLength();
if (currentSize != size) {
RealmModel realm = session.realms().getRealm(model.getParentId());
generateKeys(realm, model, size);
logger.debugv("Key size changed, generating new keys for {0}", realm.getName());
}
}
}
private void generateKeys(RealmModel realm, ComponentModel model, int size) {
KeyPair keyPair;
try {
keyPair = KeyUtils.generateRsaKeyPair(size);
model.put(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate()));
} catch (Throwable t) {
throw new ComponentValidationException("Failed to generate keys", t);
}
generateCertificate(realm, model, keyPair);
}
private void generateCertificate(RealmModel realm, ComponentModel model, KeyPair keyPair) {
try {
Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName());
model.put(Attributes.CERTIFICATE_KEY, PemUtils.encodeCertificate(certificate));
} catch (Throwable t) {
throw new ComponentValidationException("Failed to generate certificate", t);
}
}
@Override
public String getHelpText() {
return HELP_TEXT;
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return CONFIG_PROPERTIES;
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return ID;
}
}

View file

@ -0,0 +1,71 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.keys;
import org.jboss.logging.Logger;
import org.keycloak.common.util.CertificateUtils;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.RealmModel;
import java.io.FileInputStream;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JavaKeystoreKeyProvider extends AbstractRsaKeyProvider {
private static final Logger logger = Logger.getLogger(JavaKeystoreKeyProvider.class);
public JavaKeystoreKeyProvider(RealmModel realm, ComponentModel model) {
super(realm, model);
}
@Override
protected Keys loadKeys(RealmModel realm, ComponentModel model) {
try {
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream(model.get(JavaKeystoreKeyProviderFactory.KEYSTORE_KEY)), model.get(JavaKeystoreKeyProviderFactory.KEYSTORE_PASSWORD_KEY).toCharArray());
PrivateKey privateKey = (PrivateKey) keyStore.getKey(model.get(JavaKeystoreKeyProviderFactory.KEY_ALIAS_KEY), model.get(JavaKeystoreKeyProviderFactory.KEY_PASSWORD_KEY).toCharArray());
PublicKey publicKey = KeyUtils.extractPublicKey(privateKey);
KeyPair keyPair = new KeyPair(publicKey, privateKey);
X509Certificate certificate;
if (model.contains(JavaKeystoreKeyProviderFactory.CERTIFICATE_ALIAS_KEY)) {
certificate = (X509Certificate) keyStore.getCertificate(model.get(JavaKeystoreKeyProviderFactory.CERTIFICATE_ALIAS_KEY));
} else {
certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName());
}
String kid = KeyUtils.createKeyId(keyPair.getPublic());
return new Keys(kid, keyPair, certificate);
} catch (Exception e) {
throw new RuntimeException("Failed to load keys", e);
}
}
}

View file

@ -0,0 +1,115 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.keys;
import org.keycloak.Config;
import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ConfigurationValidationHelper;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.List;
import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactory {
public static final String ID = "java-keystore";
public static String KEYSTORE_KEY = "keystore";
public static ProviderConfigProperty KEYSTORE_PROPERTY = new ProviderConfigProperty(KEYSTORE_KEY, "Keystore", "Path to keys file", STRING_TYPE, null);
public static String KEYSTORE_PASSWORD_KEY = "keystorePassword";
public static ProviderConfigProperty KEYSTORE_PASSWORD_PROPERTY = new ProviderConfigProperty(KEYSTORE_PASSWORD_KEY, "Keystore Password", "Password for the keys", STRING_TYPE, null, true);
public static String KEY_ALIAS_KEY = "keyAlias";
public static ProviderConfigProperty KEY_ALIAS_PROPERTY = new ProviderConfigProperty(KEY_ALIAS_KEY, "Private Key Alias", "Alias for the private key", STRING_TYPE, null);
public static String KEY_PASSWORD_KEY = "keyPassword";
public static ProviderConfigProperty KEY_PASSWORD_PROPERTY = new ProviderConfigProperty(KEY_PASSWORD_KEY, "Private Key password", "Password for the private key", STRING_TYPE, null, true);
public static String CERTIFICATE_ALIAS_KEY = "certificateAlias";
public static ProviderConfigProperty CERTIFICATE_ALIAS_PROPERTY = new ProviderConfigProperty(CERTIFICATE_ALIAS_KEY, "Certificate Alias", "Alias for the certificate", STRING_TYPE, null);
private static final String HELP_TEXT = "Loads keys from a Java keys file";
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = AbstractRsaKeyProviderFactory.configurationBuilder()
.property(KEYSTORE_PROPERTY)
.property(KEYSTORE_PASSWORD_PROPERTY)
.property(KEY_ALIAS_PROPERTY)
.property(KEY_PASSWORD_PROPERTY)
.property(CERTIFICATE_ALIAS_PROPERTY)
.build();
@Override
public KeyProvider create(KeycloakSession session, ComponentModel model) {
return new JavaKeystoreKeyProvider(session.getContext().getRealm(), model);
}
@Override
public void validateConfiguration(KeycloakSession session, ComponentModel model) throws ComponentValidationException {
super.validateConfiguration(session, model);
ConfigurationValidationHelper.check(model)
.checkSingle(KEYSTORE_PROPERTY, true)
.checkSingle(KEYSTORE_PASSWORD_PROPERTY, true)
.checkSingle(KEY_ALIAS_PROPERTY, true)
.checkSingle(KEY_PASSWORD_PROPERTY, true)
.checkSingle(CERTIFICATE_ALIAS_PROPERTY, false);
try {
new JavaKeystoreKeyProvider(session.getContext().getRealm(), model)
.loadKeys(session.getContext().getRealm(), model);
} catch (Throwable t) {
throw new ComponentValidationException("Failed to load keys", t);
}
}
@Override
public String getHelpText() {
return HELP_TEXT;
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return CONFIG_PROPERTIES;
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return ID;
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.keys;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.PemUtils;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.RealmModel;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class RsaKeyProvider extends AbstractRsaKeyProvider {
public RsaKeyProvider(RealmModel realm, ComponentModel model) {
super(realm, model);
}
@Override
public Keys loadKeys(RealmModel realm, ComponentModel model) {
String privateRsaKeyPem = model.getConfig().getFirst(Attributes.PRIVATE_KEY_KEY);
String certificatePem = model.getConfig().getFirst(Attributes.CERTIFICATE_KEY);
PrivateKey privateKey = PemUtils.decodePrivateKey(privateRsaKeyPem);
PublicKey publicKey = KeyUtils.extractPublicKey(privateKey);
KeyPair keyPair = new KeyPair(publicKey, privateKey);
X509Certificate certificate = PemUtils.decodeCertificate(certificatePem);
String kid = KeyUtils.createKeyId(keyPair.getPublic());
return new Keys(kid, keyPair, certificate);
}
}

View file

@ -0,0 +1,128 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.keys;
import org.keycloak.Config;
import org.keycloak.common.util.CertificateUtils;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.PemUtils;
import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ConfigurationValidationHelper;
import org.keycloak.provider.ProviderConfigProperty;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class RsaKeyProviderFactory extends AbstractRsaKeyProviderFactory {
public static final String ID = "rsa";
private static final String HELP_TEXT = "RSA key provider that can optionally generated a self-signed certificate";
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = AbstractRsaKeyProviderFactory.configurationBuilder()
.property(Attributes.PRIVATE_KEY_PROPERTY)
.property(Attributes.CERTIFICATE_PROPERTY)
.build();
@Override
public KeyProvider create(KeycloakSession session, ComponentModel model) {
return new RsaKeyProvider(session.getContext().getRealm(), model);
}
@Override
public void validateConfiguration(KeycloakSession session, ComponentModel model) throws ComponentValidationException {
super.validateConfiguration(session, model);
ConfigurationValidationHelper.check(model)
.checkSingle(Attributes.PRIVATE_KEY_PROPERTY, true)
.checkSingle(Attributes.CERTIFICATE_PROPERTY, false);
KeyPair keyPair;
try {
PrivateKey privateKey = PemUtils.decodePrivateKey(model.get(Attributes.PRIVATE_KEY_KEY));
PublicKey publicKey = KeyUtils.extractPublicKey(privateKey);
keyPair = new KeyPair(publicKey, privateKey);
} catch (Throwable t) {
throw new ComponentValidationException("Failed to decode private key", t);
}
if (model.contains(Attributes.CERTIFICATE_KEY)) {
Certificate certificate = null;
try {
certificate = PemUtils.decodeCertificate(model.get(Attributes.CERTIFICATE_KEY));
} catch (Throwable t) {
throw new ComponentValidationException("Failed to decode certificate", t);
}
if (certificate == null) {
throw new ComponentValidationException("Failed to decode certificate");
}
if (!certificate.getPublicKey().equals(keyPair.getPublic())) {
throw new ComponentValidationException("Certificate does not match private key");
}
} else {
try {
RealmModel realm = session.realms().getRealm(model.getParentId());
Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName());
model.put(Attributes.CERTIFICATE_KEY, PemUtils.encodeCertificate(certificate));
} catch (Throwable t) {
throw new ComponentValidationException("Failed to generate self-signed certificate");
}
}
}
@Override
public String getHelpText() {
return HELP_TEXT;
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return CONFIG_PROPERTIES;
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return ID;
}
}

View file

@ -23,9 +23,9 @@ import java.util.Collections;
import java.util.Map; import java.util.Map;
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator; import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.jose.jwk.JSONWebKeySet; import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.jose.jwk.JWK; import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jwk.JWKBuilder;
import org.keycloak.keys.PublicKeyLoader; import org.keycloak.keys.PublicKeyLoader;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
@ -69,7 +69,7 @@ public class ClientPublicKeyLoader implements PublicKeyLoader {
PublicKey publicKey = getSignatureValidationKey(certInfo); PublicKey publicKey = getSignatureValidationKey(certInfo);
// Check if we have kid in DB, generate otherwise // Check if we have kid in DB, generate otherwise
String kid = certInfo.getKid() != null ? certInfo.getKid() : JWKBuilder.createKeyId(publicKey); String kid = certInfo.getKid() != null ? certInfo.getKid() : KeyUtils.createKeyId(publicKey);
return Collections.singletonMap(kid, publicKey); return Collections.singletonMap(kid, publicKey);
} catch (ModelException me) { } catch (ModelException me) {
logger.warnf(me, "Unable to retrieve publicKey for verify signature of client '%s' . Error details: %s", client.getClientId(), me.getMessage()); logger.warnf(me, "Unable to retrieve publicKey for verify signature of client '%s' . Error details: %s", client.getClientId(), me.getMessage());

View file

@ -22,10 +22,10 @@ import java.util.Collections;
import java.util.Map; import java.util.Map;
import org.keycloak.broker.oidc.OIDCIdentityProviderConfig; import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.PemUtils; import org.keycloak.common.util.PemUtils;
import org.keycloak.jose.jwk.JSONWebKeySet; import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.jose.jwk.JWK; import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jwk.JWKBuilder;
import org.keycloak.keys.PublicKeyLoader; import org.keycloak.keys.PublicKeyLoader;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.protocol.oidc.utils.JWKSHttpUtils; import org.keycloak.protocol.oidc.utils.JWKSHttpUtils;
@ -60,7 +60,7 @@ public class OIDCIdentityProviderPublicKeyLoader implements PublicKeyLoader {
return Collections.emptyMap(); return Collections.emptyMap();
} }
String kid = JWKBuilder.createKeyId(publicKey); String kid = KeyUtils.createKeyId(publicKey);
return Collections.singletonMap(kid, publicKey); return Collections.singletonMap(kid, publicKey);
} catch (Exception e) { } catch (Exception e) {
logger.warnf(e, "Unable to retrieve publicKey for verify signature of identityProvider '%s' . Error details: %s", config.getAlias(), e.getMessage()); logger.warnf(e, "Unable to retrieve publicKey for verify signature of identityProvider '%s' . Error details: %s", config.getAlias(), e.getMessage());

View file

@ -118,7 +118,7 @@ public abstract class AuthorizationEndpointBase {
return processor.finishAuthentication(protocol); return processor.finishAuthentication(protocol);
} else { } else {
try { try {
RestartLoginCookie.setRestartCookie(realm, clientConnection, uriInfo, clientSession); RestartLoginCookie.setRestartCookie(session, realm, clientConnection, uriInfo, clientSession);
if (redirectToAuthentication) { if (redirectToAuthentication) {
return processor.redirectToFlow(); return processor.redirectToFlow();
} }

View file

@ -22,8 +22,10 @@ import org.keycloak.common.ClientConnection;
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.crypto.HMACProvider; import org.keycloak.jose.jws.crypto.HMACProvider;
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;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.services.ServicesLogger; import org.keycloak.services.ServicesLogger;
@ -33,6 +35,7 @@ import org.keycloak.services.util.CookieHelper;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.ws.rs.core.Cookie; import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import java.security.PublicKey;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -112,11 +115,12 @@ public class RestartLoginCookie {
this.action = action; this.action = action;
} }
public String encode(RealmModel realm) { public String encode(KeycloakSession session, RealmModel realm) {
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
JWSBuilder builder = new JWSBuilder(); JWSBuilder builder = new JWSBuilder();
return builder.jsonContent(this) return builder.kid(keys.getKid()).jsonContent(this)
.hmac256((SecretKey)realm.getCodeSecretKey()); .rsa256(keys.getPrivateKey());
//.rsa256(realm.getPrivateKey());
} }
@ -133,11 +137,9 @@ public class RestartLoginCookie {
} }
} }
public static void setRestartCookie(RealmModel realm, ClientConnection connection, UriInfo uriInfo, ClientSessionModel clientSession) { public static void setRestartCookie(KeycloakSession session, RealmModel realm, ClientConnection connection, UriInfo uriInfo, ClientSessionModel clientSession) {
RestartLoginCookie restart = new RestartLoginCookie(clientSession); RestartLoginCookie restart = new RestartLoginCookie(clientSession);
String encoded = restart.encode(realm); String encoded = restart.encode(session, realm);
int keySize = realm.getCodeSecret().length();
int size = encoded.length();
String path = AuthenticationManager.getRealmCookiePath(realm, uriInfo); String path = AuthenticationManager.getRealmCookiePath(realm, uriInfo);
boolean secureOnly = realm.getSslRequired().isRequired(connection); boolean secureOnly = realm.getSslRequired().isRequired(connection);
CookieHelper.addCookie(KC_RESTART, encoded, path, null, null, -1, secureOnly, true); CookieHelper.addCookie(KC_RESTART, encoded, path, null, null, -1, secureOnly, true);
@ -157,13 +159,8 @@ public class RestartLoginCookie {
} }
String encodedCookie = cook.getValue(); String encodedCookie = cook.getValue();
JWSInput input = new JWSInput(encodedCookie); JWSInput input = new JWSInput(encodedCookie);
/* PublicKey publicKey = session.keys().getPublicKey(realm, input.getHeader().getKeyId());
if (!RSAProvider.verify(input, realm.getPublicKey())) { if (!RSAProvider.verify(input, publicKey)) {
logger.debug("Failed to verify encoded RestartLoginCookie");
return null;
}
*/
if (!HMACProvider.verify(input, (SecretKey)realm.getCodeSecretKey())) {
logger.debug("Failed to verify encoded RestartLoginCookie"); logger.debug("Failed to verify encoded RestartLoginCookie");
return null; return null;
} }

View file

@ -24,10 +24,12 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.services.ErrorResponseException; import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.Urls;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.security.PublicKey;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -46,13 +48,33 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi
public Response introspect(String token) { public Response introspect(String token) {
try { try {
AccessToken toIntrospect = toAccessToken(token); boolean valid = true;
RSATokenVerifier verifier = RSATokenVerifier.create(token)
.realmUrl(Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName()));
PublicKey publicKey = session.keys().getPublicKey(realm, verifier.getHeader().getKeyId());
if (publicKey == null) {
valid = false;
} else {
try {
verifier.publicKey(publicKey);
verifier.verify();
} catch (VerificationException e) {
valid = false;
}
}
RealmModel realm = this.session.getContext().getRealm(); RealmModel realm = this.session.getContext().getRealm();
ObjectNode tokenMetadata; ObjectNode tokenMetadata;
boolean active = tokenManager.isTokenValid(session, realm, toIntrospect); AccessToken toIntrospect = verifier.getToken();
if (active) { if (valid) {
valid = tokenManager.isTokenValid(session, realm, toIntrospect);
}
if (valid) {
tokenMetadata = JsonSerialization.createObjectNode(toIntrospect); tokenMetadata = JsonSerialization.createObjectNode(toIntrospect);
tokenMetadata.put("client_id", toIntrospect.getIssuedFor()); tokenMetadata.put("client_id", toIntrospect.getIssuedFor());
tokenMetadata.put("username", toIntrospect.getPreferredUsername()); tokenMetadata.put("username", toIntrospect.getPreferredUsername());
@ -60,7 +82,7 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi
tokenMetadata = JsonSerialization.createObjectNode(); tokenMetadata = JsonSerialization.createObjectNode();
} }
tokenMetadata.put("active", active); tokenMetadata.put("active", valid);
return Response.ok(JsonSerialization.writeValueAsBytes(tokenMetadata)).type(MediaType.APPLICATION_JSON_TYPE).build(); return Response.ok(JsonSerialization.writeValueAsBytes(tokenMetadata)).type(MediaType.APPLICATION_JSON_TYPE).build();
} catch (Exception e) { } catch (Exception e) {
@ -70,7 +92,13 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi
protected AccessToken toAccessToken(String token) { protected AccessToken toAccessToken(String token) {
try { try {
return RSATokenVerifier.toAccessToken(token, realm.getPublicKey()); RSATokenVerifier verifier = RSATokenVerifier.create(token)
.realmUrl(Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName()));
PublicKey publicKey = session.keys().getPublicKey(realm, verifier.getHeader().getKeyId());
verifier.publicKey(publicKey);
return verifier.verify().getToken();
} catch (VerificationException e) { } catch (VerificationException e) {
throw new ErrorResponseException("invalid_request", "Invalid token.", Response.Status.UNAUTHORIZED); throw new ErrorResponseException("invalid_request", "Invalid token.", Response.Status.UNAUTHORIZED);
} }

View file

@ -20,10 +20,10 @@ package org.keycloak.protocol.oidc;
import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.jose.jwk.JWK; import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jwk.JWKBuilder; import org.keycloak.jose.jwk.JWKBuilder;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.keys.KeyMetadata;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpoint; import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpoint;
@ -31,6 +31,7 @@ import org.keycloak.protocol.oidc.endpoints.LoginStatusIframeEndpoint;
import org.keycloak.protocol.oidc.endpoints.LogoutEndpoint; import org.keycloak.protocol.oidc.endpoints.LogoutEndpoint;
import org.keycloak.protocol.oidc.endpoints.TokenEndpoint; import org.keycloak.protocol.oidc.endpoints.TokenEndpoint;
import org.keycloak.protocol.oidc.endpoints.UserInfoEndpoint; import org.keycloak.protocol.oidc.endpoints.UserInfoEndpoint;
import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.services.ServicesLogger; import org.keycloak.services.ServicesLogger;
import org.keycloak.services.resources.RealmsResource; import org.keycloak.services.resources.RealmsResource;
@ -44,6 +45,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import java.util.List;
/** /**
* Resource class for the oauth/openid connect token service * Resource class for the oauth/openid connect token service
@ -174,8 +176,16 @@ public class OIDCLoginProtocolService {
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@NoCache @NoCache
public JSONWebKeySet certs() { public JSONWebKeySet certs() {
List<KeyMetadata> publicKeys = session.keys().getKeys(realm, false);
JWK[] keys = new JWK[publicKeys.size()];
int i = 0;
for (KeyMetadata k : publicKeys) {
keys[i++] = JWKBuilder.create().kid(k.getKid()).rs256(k.getPublicKey());
}
JSONWebKeySet keySet = new JSONWebKeySet(); JSONWebKeySet keySet = new JSONWebKeySet();
keySet.setKeys(new JWK[]{JWKBuilder.create().rs256(realm.getPublicKey())}); keySet.setKeys(keys);
return keySet; return keySet;
} }

View file

@ -17,11 +17,11 @@
package org.keycloak.protocol.oidc; package org.keycloak.protocol.oidc;
import org.keycloak.OAuth2Constants; import org.jboss.logging.Logger;
import org.keycloak.OAuthErrorException;
import org.keycloak.cluster.ClusterProvider; import org.keycloak.cluster.ClusterProvider;
import org.keycloak.common.ClientConnection; import org.keycloak.common.ClientConnection;
import org.keycloak.common.util.Time; import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
@ -35,6 +35,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.ClientTemplateModel; import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.GroupModel; import org.keycloak.models.GroupModel;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.ProtocolMapperModel;
@ -55,11 +56,11 @@ import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import org.keycloak.representations.RefreshToken; import org.keycloak.representations.RefreshToken;
import org.keycloak.services.ErrorResponseException; import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.UserSessionManager; import org.keycloak.services.managers.UserSessionManager;
import org.keycloak.util.TokenUtil; import org.keycloak.util.TokenUtil;
import org.keycloak.common.util.Time;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
@ -80,7 +81,8 @@ import java.util.Set;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class TokenManager { public class TokenManager {
protected static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER; private static final Logger logger = Logger.getLogger(TokenManager.class);
private static final String JWT = "JWT";
// Harcoded for now // Harcoded for now
Algorithm jwsAlgorithm = Algorithm.RS256; Algorithm jwsAlgorithm = Algorithm.RS256;
@ -213,7 +215,7 @@ public class TokenManager {
} }
public RefreshResult refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel authorizedClient, String encodedRefreshToken, EventBuilder event, HttpHeaders headers) throws OAuthErrorException { public RefreshResult refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel authorizedClient, String encodedRefreshToken, EventBuilder event, HttpHeaders headers) throws OAuthErrorException {
RefreshToken refreshToken = verifyRefreshToken(realm, encodedRefreshToken); RefreshToken refreshToken = verifyRefreshToken(session, realm, encodedRefreshToken);
event.user(refreshToken.getSubject()).session(refreshToken.getSessionState()) event.user(refreshToken.getSubject()).session(refreshToken.getSessionState())
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId()) .detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
@ -248,13 +250,13 @@ public class TokenManager {
return new RefreshResult(res, TokenUtil.TOKEN_TYPE_OFFLINE.equals(refreshToken.getType())); return new RefreshResult(res, TokenUtil.TOKEN_TYPE_OFFLINE.equals(refreshToken.getType()));
} }
public RefreshToken verifyRefreshToken(RealmModel realm, String encodedRefreshToken) throws OAuthErrorException { public RefreshToken verifyRefreshToken(KeycloakSession session, RealmModel realm, String encodedRefreshToken) throws OAuthErrorException {
return verifyRefreshToken(realm, encodedRefreshToken, true); return verifyRefreshToken(session, realm, encodedRefreshToken, true);
} }
public RefreshToken verifyRefreshToken(RealmModel realm, String encodedRefreshToken, boolean checkExpiration) throws OAuthErrorException { public RefreshToken verifyRefreshToken(KeycloakSession session, RealmModel realm, String encodedRefreshToken, boolean checkExpiration) throws OAuthErrorException {
try { try {
RefreshToken refreshToken = toRefreshToken(realm, encodedRefreshToken); RefreshToken refreshToken = toRefreshToken(session, realm, encodedRefreshToken);
if (checkExpiration) { if (checkExpiration) {
if (refreshToken.getExpiration() != 0 && refreshToken.isExpired()) { if (refreshToken.getExpiration() != 0 && refreshToken.isExpired()) {
@ -272,21 +274,21 @@ public class TokenManager {
} }
} }
public RefreshToken toRefreshToken(RealmModel realm, String encodedRefreshToken) throws JWSInputException, OAuthErrorException { public RefreshToken toRefreshToken(KeycloakSession session, RealmModel realm, String encodedRefreshToken) throws JWSInputException, OAuthErrorException {
JWSInput jws = new JWSInput(encodedRefreshToken); JWSInput jws = new JWSInput(encodedRefreshToken);
if (!RSAProvider.verify(jws, realm.getPublicKey())) { if (!RSAProvider.verify(jws, session.keys().getPublicKey(realm, jws.getHeader().getKeyId()))) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token"); throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token");
} }
return jws.readJsonContent(RefreshToken.class); return jws.readJsonContent(RefreshToken.class);
} }
public IDToken verifyIDToken(RealmModel realm, String encodedIDToken) throws OAuthErrorException { public IDToken verifyIDToken(KeycloakSession session, RealmModel realm, String encodedIDToken) throws OAuthErrorException {
try { try {
JWSInput jws = new JWSInput(encodedIDToken); JWSInput jws = new JWSInput(encodedIDToken);
IDToken idToken; IDToken idToken;
if (!RSAProvider.verify(jws, realm.getPublicKey())) { if (!RSAProvider.verify(jws, session.keys().getPublicKey(realm, jws.getHeader().getKeyId()))) {
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);
@ -499,7 +501,7 @@ public class TokenManager {
public AccessToken transformAccessToken(KeycloakSession session, AccessToken token, RealmModel realm, ClientModel client, UserModel user, public AccessToken transformAccessToken(KeycloakSession session, AccessToken token, RealmModel realm, ClientModel client, UserModel user,
UserSessionModel userSession, ClientSessionModel clientSession) { UserSessionModel userSession, ClientSessionModel clientSession) {
Set<ProtocolMapperModel> mappings = new ClientSessionCode(realm, clientSession).getRequestedProtocolMappers(); Set<ProtocolMapperModel> mappings = new ClientSessionCode(session, realm, clientSession).getRequestedProtocolMappers();
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
for (ProtocolMapperModel mapping : mappings) { for (ProtocolMapperModel mapping : mappings) {
@ -513,7 +515,7 @@ public class TokenManager {
public AccessToken transformUserInfoAccessToken(KeycloakSession session, AccessToken token, RealmModel realm, ClientModel client, UserModel user, public AccessToken transformUserInfoAccessToken(KeycloakSession session, AccessToken token, RealmModel realm, ClientModel client, UserModel user,
UserSessionModel userSession, ClientSessionModel clientSession) { UserSessionModel userSession, ClientSessionModel clientSession) {
Set<ProtocolMapperModel> mappings = new ClientSessionCode(realm, clientSession).getRequestedProtocolMappers(); Set<ProtocolMapperModel> mappings = new ClientSessionCode(session, realm, clientSession).getRequestedProtocolMappers();
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
for (ProtocolMapperModel mapping : mappings) { for (ProtocolMapperModel mapping : mappings) {
@ -533,7 +535,7 @@ public class TokenManager {
public void transformIDToken(KeycloakSession session, IDToken token, RealmModel realm, ClientModel client, UserModel user, public void transformIDToken(KeycloakSession session, IDToken token, RealmModel realm, ClientModel client, UserModel user,
UserSessionModel userSession, ClientSessionModel clientSession) { UserSessionModel userSession, ClientSessionModel clientSession) {
Set<ProtocolMapperModel> mappings = new ClientSessionCode(realm, clientSession).getRequestedProtocolMappers(); Set<ProtocolMapperModel> mappings = new ClientSessionCode(session, realm, clientSession).getRequestedProtocolMappers();
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
for (ProtocolMapperModel mapping : mappings) { for (ProtocolMapperModel mapping : mappings) {
@ -618,13 +620,9 @@ public class TokenManager {
} }
public String encodeToken(RealmModel realm, Object token) { public String encodeToken(KeycloakSession session, RealmModel realm, Object token) {
String encodedToken = new JWSBuilder() KeyManager.ActiveKey activeKey = session.keys().getActiveKey(realm);
.type(OAuth2Constants.JWT) return new JWSBuilder().type(JWT).kid(activeKey.getKid()).jsonContent(token).sign(jwsAlgorithm, activeKey.getPrivateKey());
.kid(realm.getKeyId())
.jsonContent(token)
.sign(jwsAlgorithm, realm.getPrivateKey());
return encodedToken;
} }
public AccessTokenResponseBuilder responseBuilder(RealmModel realm, ClientModel client, EventBuilder event, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) { public AccessTokenResponseBuilder responseBuilder(RealmModel realm, ClientModel client, EventBuilder event, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
@ -731,6 +729,8 @@ public class TokenManager {
public AccessTokenResponse build() { public AccessTokenResponse build() {
KeyManager.ActiveKey activeKey = session.keys().getActiveKey(realm);
if (accessToken != null) { if (accessToken != null) {
event.detail(Details.TOKEN_ID, accessToken.getId()); event.detail(Details.TOKEN_ID, accessToken.getId());
} }
@ -746,7 +746,7 @@ public class TokenManager {
AccessTokenResponse res = new AccessTokenResponse(); AccessTokenResponse res = new AccessTokenResponse();
if (accessToken != null) { if (accessToken != null) {
String encodedToken = new JWSBuilder().type(OAuth2Constants.JWT).kid(realm.getKeyId()).jsonContent(accessToken).sign(jwsAlgorithm, realm.getPrivateKey()); String encodedToken = new JWSBuilder().type(JWT).kid(activeKey.getKid()).jsonContent(accessToken).sign(jwsAlgorithm, activeKey.getPrivateKey());
res.setToken(encodedToken); res.setToken(encodedToken);
res.setTokenType("bearer"); res.setTokenType("bearer");
res.setSessionState(accessToken.getSessionState()); res.setSessionState(accessToken.getSessionState());
@ -764,11 +764,11 @@ public class TokenManager {
} }
if (idToken != null) { if (idToken != null) {
String encodedToken = new JWSBuilder().type(OAuth2Constants.JWT).kid(realm.getKeyId()).jsonContent(idToken).sign(jwsAlgorithm, realm.getPrivateKey()); String encodedToken = new JWSBuilder().type(JWT).kid(activeKey.getKid()).jsonContent(idToken).sign(jwsAlgorithm, activeKey.getPrivateKey());
res.setIdToken(encodedToken); res.setIdToken(encodedToken);
} }
if (refreshToken != null) { if (refreshToken != null) {
String encodedToken = new JWSBuilder().type(OAuth2Constants.JWT).kid(realm.getKeyId()).jsonContent(refreshToken).sign(jwsAlgorithm, realm.getPrivateKey()); String encodedToken = new JWSBuilder().type(JWT).kid(activeKey.getKid()).jsonContent(refreshToken).sign(jwsAlgorithm, activeKey.getPrivateKey());
res.setRefreshToken(encodedToken); res.setRefreshToken(encodedToken);
if (refreshToken.getExpiration() != 0) { if (refreshToken.getExpiration() != 0) {
res.setRefreshExpiresIn(refreshToken.getExpiration() - Time.currentTime()); res.setRefreshExpiresIn(refreshToken.getExpiration() - Time.currentTime());

View file

@ -116,7 +116,7 @@ public class LogoutEndpoint {
boolean error = false; boolean error = false;
if (encodedIdToken != null) { if (encodedIdToken != null) {
try { try {
IDToken idToken = tokenManager.verifyIDToken(realm, encodedIdToken); IDToken idToken = tokenManager.verifyIDToken(session, realm, encodedIdToken);
userSession = session.sessions().getUserSession(realm, idToken.getSessionState()); userSession = session.sessions().getUserSession(realm, idToken.getSessionState());
if (userSession == null) { if (userSession == null) {
error = true; error = true;
@ -187,7 +187,7 @@ public class LogoutEndpoint {
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "No refresh token", Response.Status.BAD_REQUEST); throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "No refresh token", Response.Status.BAD_REQUEST);
} }
try { try {
RefreshToken token = tokenManager.verifyRefreshToken(realm, refreshToken, false); RefreshToken token = tokenManager.verifyRefreshToken(session, realm, refreshToken, false);
UserSessionModel userSessionModel = session.sessions().getUserSession(realm, token.getSessionState()); UserSessionModel userSessionModel = session.sessions().getUserSession(realm, token.getSessionState());
if (userSessionModel != null) { if (userSessionModel != null) {
logout(userSessionModel); logout(userSessionModel);

View file

@ -68,9 +68,6 @@ import java.util.Map;
*/ */
public class TokenEndpoint { public class TokenEndpoint {
// Flag if code was already exchanged for token
private static final String CODE_EXCHANGED = "CODE_EXCHANGED";
private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER; private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
private MultivaluedMap<String, String> formParams; private MultivaluedMap<String, String> formParams;
private ClientModel client; private ClientModel client;
@ -203,34 +200,29 @@ public class TokenEndpoint {
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Missing parameter: " + OAuth2Constants.CODE, Response.Status.BAD_REQUEST); throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Missing parameter: " + OAuth2Constants.CODE, Response.Status.BAD_REQUEST);
} }
ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm); ClientSessionCode.ParseResult parseResult = ClientSessionCode.parseResult(code, session, realm);
if (accessCode == null) { if (parseResult.isClientSessionNotFound() || parseResult.isIllegalHash()) {
String[] parts = code.split("\\."); String[] parts = code.split("\\.");
if (parts.length == 2) { if (parts.length == 2) {
event.detail(Details.CODE_ID, parts[1]); event.detail(Details.CODE_ID, parts[1]);
} }
event.error(Errors.INVALID_CODE); event.error(Errors.INVALID_CODE);
throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Code not found", Response.Status.BAD_REQUEST); if (parseResult.getClientSession() != null) {
session.sessions().removeClientSession(realm, parseResult.getClientSession());
}
throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Code not valid", Response.Status.BAD_REQUEST);
} }
ClientSessionModel clientSession = accessCode.getClientSession(); ClientSessionModel clientSession = parseResult.getClientSession();
event.detail(Details.CODE_ID, clientSession.getId()); event.detail(Details.CODE_ID, clientSession.getId());
String codeExchanged = clientSession.getNote(CODE_EXCHANGED); if (!parseResult.getCode().isValid(ClientSessionModel.Action.CODE_TO_TOKEN.name(), ClientSessionCode.ActionType.CLIENT)) {
if (codeExchanged != null && Boolean.parseBoolean(codeExchanged)) {
session.sessions().removeClientSession(realm, clientSession);
event.error(Errors.INVALID_CODE);
throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Code used already", Response.Status.BAD_REQUEST);
}
if (!accessCode.isValid(ClientSessionModel.Action.CODE_TO_TOKEN.name(), ClientSessionCode.ActionType.CLIENT)) {
event.error(Errors.INVALID_CODE); event.error(Errors.INVALID_CODE);
throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Code is expired", Response.Status.BAD_REQUEST); throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Code is expired", Response.Status.BAD_REQUEST);
} }
accessCode.setAction(null); parseResult.getCode().setAction(null);
clientSession.setNote(CODE_EXCHANGED, "true");
UserSessionModel userSession = clientSession.getUserSession(); UserSessionModel userSession = clientSession.getUserSession();
if (userSession == null) { if (userSession == null) {
@ -275,7 +267,7 @@ public class TokenEndpoint {
updateClientSession(clientSession); updateClientSession(clientSession);
updateUserSessionFromClientAuth(userSession); updateUserSessionFromClientAuth(userSession);
AccessToken token = tokenManager.createClientAccessToken(session, accessCode.getRequestedRoles(), realm, client, user, userSession, clientSession); AccessToken token = tokenManager.createClientAccessToken(session, parseResult.getCode().getRequestedRoles(), realm, client, user, userSession, clientSession);
AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession) AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
.accessToken(token) .accessToken(token)

View file

@ -129,7 +129,11 @@ public class UserInfoEndpoint {
AccessToken token = null; AccessToken token = null;
try { try {
token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()), true, true); RSATokenVerifier verifier = RSATokenVerifier.create(tokenString)
.realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
String kid = verifier.getHeader().getKeyId();
verifier.publicKey(session.keys().getPublicKey(realm, kid));
token = verifier.verify().getToken();
} catch (VerificationException e) { } catch (VerificationException e) {
event.error(Errors.INVALID_TOKEN); event.error(Errors.INVALID_TOKEN);
throw new ErrorResponseException(OAuthErrorException.INVALID_TOKEN, "Token invalid: " + e.getMessage(), Response.Status.UNAUTHORIZED); throw new ErrorResponseException(OAuthErrorException.INVALID_TOKEN, "Token invalid: " + e.getMessage(), Response.Status.UNAUTHORIZED);
@ -190,7 +194,7 @@ public class UserInfoEndpoint {
claims.put("aud", audience); claims.put("aud", audience);
Algorithm signatureAlg = cfg.getUserInfoSignedResponseAlg(); Algorithm signatureAlg = cfg.getUserInfoSignedResponseAlg();
PrivateKey privateKey = realm.getPrivateKey(); PrivateKey privateKey = session.keys().getActiveKey(realm).getPrivateKey();
String signedUserInfo = new JWSBuilder() String signedUserInfo = new JWSBuilder()
.jsonContent(claims) .jsonContent(claims)

View file

@ -32,6 +32,7 @@ import org.keycloak.dom.saml.v2.protocol.ResponseType;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
@ -393,19 +394,22 @@ public class SamlProtocol implements LoginProtocol {
JaxrsSAML2BindingBuilder bindingBuilder = new JaxrsSAML2BindingBuilder(); JaxrsSAML2BindingBuilder bindingBuilder = new JaxrsSAML2BindingBuilder();
bindingBuilder.relayState(relayState); bindingBuilder.relayState(relayState);
KeyManager keyManager = session.keys();
KeyManager.ActiveKey keys = keyManager.getActiveKey(realm);
if (samlClient.requiresRealmSignature()) { if (samlClient.requiresRealmSignature()) {
String canonicalization = samlClient.getCanonicalizationMethod(); String canonicalization = samlClient.getCanonicalizationMethod();
if (canonicalization != null) { if (canonicalization != null) {
bindingBuilder.canonicalizationMethod(canonicalization); bindingBuilder.canonicalizationMethod(canonicalization);
} }
bindingBuilder.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()).signDocument(); bindingBuilder.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
} }
if (samlClient.requiresAssertionSignature()) { if (samlClient.requiresAssertionSignature()) {
String canonicalization = samlClient.getCanonicalizationMethod(); String canonicalization = samlClient.getCanonicalizationMethod();
if (canonicalization != null) { if (canonicalization != null) {
bindingBuilder.canonicalizationMethod(canonicalization); bindingBuilder.canonicalizationMethod(canonicalization);
} }
bindingBuilder.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()).signAssertions(); bindingBuilder.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signAssertions();
} }
if (samlClient.requiresEncryption()) { if (samlClient.requiresEncryption()) {
PublicKey publicKey = null; PublicKey publicKey = null;
@ -536,7 +540,8 @@ public class SamlProtocol implements LoginProtocol {
if (canonicalization != null) { if (canonicalization != null) {
binding.canonicalizationMethod(canonicalization); binding.canonicalizationMethod(canonicalization);
} }
binding.signatureAlgorithm(algorithm).signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()).signDocument(); KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
binding.signatureAlgorithm(algorithm).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
} }
try { try {
@ -633,7 +638,8 @@ public class SamlProtocol implements LoginProtocol {
private JaxrsSAML2BindingBuilder createBindingBuilder(SamlClient samlClient) { private JaxrsSAML2BindingBuilder createBindingBuilder(SamlClient samlClient) {
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder(); JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder();
if (samlClient.requiresRealmSignature()) { if (samlClient.requiresRealmSignature()) {
binding.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()).signDocument(); KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
binding.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
} }
return binding; return binding;
} }

View file

@ -21,6 +21,7 @@ import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.common.VerificationException; import org.keycloak.common.VerificationException;
import org.keycloak.common.util.PemUtils;
import org.keycloak.common.util.StreamUtil; import org.keycloak.common.util.StreamUtil;
import org.keycloak.dom.saml.v2.SAML2Object; import org.keycloak.dom.saml.v2.SAML2Object;
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType; import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
@ -34,6 +35,8 @@ import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.AuthorizationEndpointBase; import org.keycloak.protocol.AuthorizationEndpointBase;
@ -59,6 +62,7 @@ import javax.ws.rs.Path;
import javax.ws.rs.PathParam; import javax.ws.rs.PathParam;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam; import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
@ -77,6 +81,9 @@ public class SamlService extends AuthorizationEndpointBase {
protected static final Logger logger = Logger.getLogger(SamlService.class); protected static final Logger logger = Logger.getLogger(SamlService.class);
@Context
protected KeycloakSession session;
public SamlService(RealmModel realm, EventBuilder event) { public SamlService(RealmModel realm, EventBuilder event) {
super(realm, event); super(realm, event);
} }
@ -374,7 +381,8 @@ public class SamlService extends AuthorizationEndpointBase {
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(logoutRelayState); JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(logoutRelayState);
if (samlClient.requiresRealmSignature()) { if (samlClient.requiresRealmSignature()) {
SignatureAlgorithm algorithm = samlClient.getSignatureAlgorithm(); SignatureAlgorithm algorithm = samlClient.getSignatureAlgorithm();
binding.signatureAlgorithm(algorithm).signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()).signDocument(); KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
binding.signatureAlgorithm(algorithm).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
} }
try { try {
@ -508,18 +516,18 @@ public class SamlService extends AuthorizationEndpointBase {
@Produces(MediaType.APPLICATION_XML) @Produces(MediaType.APPLICATION_XML)
@NoCache @NoCache
public String getDescriptor() throws Exception { public String getDescriptor() throws Exception {
return getIDPMetadataDescriptor(uriInfo, realm); return getIDPMetadataDescriptor(uriInfo, session, realm);
} }
public static String getIDPMetadataDescriptor(UriInfo uriInfo, RealmModel realm) throws IOException { public static String getIDPMetadataDescriptor(UriInfo uriInfo, KeycloakSession session, RealmModel realm) throws IOException {
InputStream is = SamlService.class.getResourceAsStream("/idp-metadata-template.xml"); InputStream is = SamlService.class.getResourceAsStream("/idp-metadata-template.xml");
String template = StreamUtil.readString(is); String template = StreamUtil.readString(is);
template = template.replace("${idp.entityID}", RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString()); template = template.replace("${idp.entityID}", RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString());
template = template.replace("${idp.sso.HTTP-POST}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString()); template = template.replace("${idp.sso.HTTP-POST}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
template = template.replace("${idp.sso.HTTP-Redirect}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString()); template = template.replace("${idp.sso.HTTP-Redirect}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
template = template.replace("${idp.sls.HTTP-POST}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString()); template = template.replace("${idp.sls.HTTP-POST}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
template = template.replace("${idp.signing.certificate}", realm.getCertificatePem()); template = template.replace("${idp.signing.certificate}", PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate()));
return template; return template;
} }

View file

@ -18,6 +18,7 @@
package org.keycloak.protocol.saml.installation; package org.keycloak.protocol.saml.installation;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.common.util.PemUtils;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
@ -43,12 +44,12 @@ public class KeycloakSamlClientInstallation implements ClientInstallationProvide
SamlClient samlClient = new SamlClient(client); SamlClient samlClient = new SamlClient(client);
StringBuffer buffer = new StringBuffer(); StringBuffer buffer = new StringBuffer();
buffer.append("<keycloak-saml-adapter>\n"); buffer.append("<keycloak-saml-adapter>\n");
baseXml(realm, client, baseUri, samlClient, buffer); baseXml(session, realm, client, baseUri, samlClient, buffer);
buffer.append("</keycloak-saml-adapter>\n"); buffer.append("</keycloak-saml-adapter>\n");
return Response.ok(buffer.toString(), MediaType.TEXT_PLAIN_TYPE).build(); return Response.ok(buffer.toString(), MediaType.TEXT_PLAIN_TYPE).build();
} }
public static void baseXml(RealmModel realm, ClientModel client, URI baseUri, SamlClient samlClient, StringBuffer buffer) { public static void baseXml(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri, SamlClient samlClient, StringBuffer buffer) {
buffer.append(" <SP entityID=\"").append(client.getClientId()).append("\"\n"); buffer.append(" <SP entityID=\"").append(client.getClientId()).append("\"\n");
buffer.append(" sslPolicy=\"").append(realm.getSslRequired().name()).append("\"\n"); buffer.append(" sslPolicy=\"").append(realm.getSslRequired().name()).append("\"\n");
buffer.append(" logoutPage=\"SPECIFY YOUR LOGOUT PAGE!\">\n"); buffer.append(" logoutPage=\"SPECIFY YOUR LOGOUT PAGE!\">\n");
@ -116,7 +117,7 @@ public class KeycloakSamlClientInstallation implements ClientInstallationProvide
buffer.append(" <Keys>\n"); buffer.append(" <Keys>\n");
buffer.append(" <Key signing=\"true\">\n"); buffer.append(" <Key signing=\"true\">\n");
buffer.append(" <CertificatePem>\n"); buffer.append(" <CertificatePem>\n");
buffer.append(" ").append(realm.getCertificatePem()).append("\n"); buffer.append(" ").append(PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate())).append("\n");
buffer.append(" </CertificatePem>\n"); buffer.append(" </CertificatePem>\n");
buffer.append(" </Key>\n"); buffer.append(" </Key>\n");
buffer.append(" </Keys>\n"); buffer.append(" </Keys>\n");

View file

@ -41,7 +41,7 @@ public class KeycloakSamlSubsystemInstallation implements ClientInstallationProv
SamlClient samlClient = new SamlClient(client); SamlClient samlClient = new SamlClient(client);
StringBuffer buffer = new StringBuffer(); StringBuffer buffer = new StringBuffer();
buffer.append("<secure-deployment name=\"YOUR-WAR.war\">\n"); buffer.append("<secure-deployment name=\"YOUR-WAR.war\">\n");
KeycloakSamlClientInstallation.baseXml(realm, client, baseUri, samlClient, buffer); KeycloakSamlClientInstallation.baseXml(session, realm, client, baseUri, samlClient, buffer);
buffer.append("</secure-deployment>\n"); buffer.append("</secure-deployment>\n");
return Response.ok(buffer.toString(), MediaType.TEXT_PLAIN_TYPE).build(); return Response.ok(buffer.toString(), MediaType.TEXT_PLAIN_TYPE).build();
} }

View file

@ -43,7 +43,7 @@ public class ModAuthMellonClientInstallation implements ClientInstallationProvid
SamlClient samlClient = new SamlClient(client); SamlClient samlClient = new SamlClient(client);
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(baos); ZipOutputStream zip = new ZipOutputStream(baos);
String idpDescriptor = SamlIDPDescriptorClientInstallation.getIDPDescriptorForClient(realm, client, serverBaseUri); String idpDescriptor = SamlIDPDescriptorClientInstallation.getIDPDescriptorForClient(session, realm, client, serverBaseUri);
String spDescriptor = SamlSPDescriptorClientInstallation.getSPDescriptorForClient(client); String spDescriptor = SamlSPDescriptorClientInstallation.getSPDescriptorForClient(client);
String clientDirName = client.getClientId() String clientDirName = client.getClientId()
.replace('/', '_') .replace('/', '_')

View file

@ -18,6 +18,7 @@
package org.keycloak.protocol.saml.installation; package org.keycloak.protocol.saml.installation;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.common.util.PemUtils;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
@ -37,7 +38,7 @@ import java.net.URI;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class SamlIDPDescriptorClientInstallation implements ClientInstallationProvider { public class SamlIDPDescriptorClientInstallation implements ClientInstallationProvider {
public static String getIDPDescriptorForClient(RealmModel realm, ClientModel client, URI serverBaseUri) { public static String getIDPDescriptorForClient(KeycloakSession session, RealmModel realm, ClientModel client, URI serverBaseUri) {
SamlClient samlClient = new SamlClient(client); SamlClient samlClient = new SamlClient(client);
String idpEntityId = RealmsResource.realmBaseUrl(UriBuilder.fromUri(serverBaseUri)).build(realm.getName()).toString(); String idpEntityId = RealmsResource.realmBaseUrl(UriBuilder.fromUri(serverBaseUri)).build(realm.getName()).toString();
String idp = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + String idp = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
@ -75,7 +76,7 @@ public class SamlIDPDescriptorClientInstallation implements ClientInstallationPr
" <dsig:KeyInfo xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">\n" + " <dsig:KeyInfo xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">\n" +
" <dsig:X509Data>\n" + " <dsig:X509Data>\n" +
" <dsig:X509Certificate>\n" + " <dsig:X509Certificate>\n" +
" " + realm.getCertificatePem() + "\n" + " " + PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate()) + "\n" +
" </dsig:X509Certificate>\n" + " </dsig:X509Certificate>\n" +
" </dsig:X509Data>\n" + " </dsig:X509Data>\n" +
" </dsig:KeyInfo>\n" + " </dsig:KeyInfo>\n" +
@ -87,7 +88,7 @@ public class SamlIDPDescriptorClientInstallation implements ClientInstallationPr
@Override @Override
public Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI serverBaseUri) { public Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI serverBaseUri) {
String descriptor = getIDPDescriptorForClient(realm, client, serverBaseUri); String descriptor = getIDPDescriptorForClient(session, realm, client, serverBaseUri);
return Response.ok(descriptor, MediaType.TEXT_PLAIN_TYPE).build(); return Response.ok(descriptor, MediaType.TEXT_PLAIN_TYPE).build();
} }

View file

@ -115,7 +115,7 @@ public class RoleListMapper extends AbstractSAMLProtocolMapper implements SAMLRo
List<SamlProtocol.ProtocolMapperProcessor<SAMLRoleNameMapper>> roleNameMappers = new LinkedList<>(); List<SamlProtocol.ProtocolMapperProcessor<SAMLRoleNameMapper>> roleNameMappers = new LinkedList<>();
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
AttributeType singleAttributeType = null; AttributeType singleAttributeType = null;
Set<ProtocolMapperModel> requestedProtocolMappers = new ClientSessionCode(clientSession.getRealm(), clientSession).getRequestedProtocolMappers(); Set<ProtocolMapperModel> requestedProtocolMappers = new ClientSessionCode(session, clientSession.getRealm(), clientSession).getRequestedProtocolMappers();
for (ProtocolMapperModel mapping : requestedProtocolMappers) { for (ProtocolMapperModel mapping : requestedProtocolMappers) {
ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper()); ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());

View file

@ -17,10 +17,12 @@
package org.keycloak.services; package org.keycloak.services;
import org.keycloak.credential.UserCredentialStoreManager; import org.keycloak.credential.UserCredentialStoreManager;
import org.keycloak.keys.DefaultKeyManager;
import org.keycloak.models.KeycloakContext; import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakTransactionManager; import org.keycloak.models.KeycloakTransactionManager;
import org.keycloak.models.KeyManager;
import org.keycloak.models.RealmProvider; import org.keycloak.models.RealmProvider;
import org.keycloak.models.UserCredentialManager; import org.keycloak.models.UserCredentialManager;
import org.keycloak.models.UserFederationManager; import org.keycloak.models.UserFederationManager;
@ -60,6 +62,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
private UserFederationManager federationManager; private UserFederationManager federationManager;
private UserFederatedStorageProvider userFederatedStorageProvider; private UserFederatedStorageProvider userFederatedStorageProvider;
private KeycloakContext context; private KeycloakContext context;
private KeyManager keyManager;
public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) { public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
this.factory = factory; this.factory = factory;
@ -221,6 +224,14 @@ public class DefaultKeycloakSession implements KeycloakSession {
return sessionProvider; return sessionProvider;
} }
@Override
public KeyManager keys() {
if (keyManager == null) {
keyManager = new DefaultKeyManager(this);
}
return keyManager;
}
public void close() { public void close() {
for (Provider p : providers.values()) { for (Provider p : providers.values()) {
try { try {

View file

@ -79,7 +79,7 @@ public class ClientRegistrationAuth {
return; return;
} }
ClientRegistrationTokenUtils.TokenVerification tokenVerification = ClientRegistrationTokenUtils.verifyToken(realm, uri, split[1]); ClientRegistrationTokenUtils.TokenVerification tokenVerification = ClientRegistrationTokenUtils.verifyToken(session, realm, uri, split[1]);
if (tokenVerification.getError() != null) { if (tokenVerification.getError() != null) {
throw unauthorized(tokenVerification.getError().getMessage()); throw unauthorized(tokenVerification.getError().getMessage());
} }

View file

@ -24,6 +24,7 @@ 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;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
@ -32,6 +33,7 @@ import org.keycloak.services.Urls;
import org.keycloak.util.TokenUtil; import org.keycloak.util.TokenUtil;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import java.security.PublicKey;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -42,21 +44,21 @@ public class ClientRegistrationTokenUtils {
public static final String TYPE_REGISTRATION_ACCESS_TOKEN = "RegistrationAccessToken"; public static final String TYPE_REGISTRATION_ACCESS_TOKEN = "RegistrationAccessToken";
public static String updateRegistrationAccessToken(KeycloakSession session, ClientModel client) { public static String updateRegistrationAccessToken(KeycloakSession session, ClientModel client) {
return updateRegistrationAccessToken(session.getContext().getRealm(), session.getContext().getUri(), client); return updateRegistrationAccessToken(session, session.getContext().getRealm(), session.getContext().getUri(), client);
} }
public static String updateRegistrationAccessToken(RealmModel realm, UriInfo uri, ClientModel client) { public static String updateRegistrationAccessToken(KeycloakSession session, RealmModel realm, UriInfo uri, ClientModel client) {
String id = KeycloakModelUtils.generateId(); String id = KeycloakModelUtils.generateId();
client.setRegistrationToken(id); client.setRegistrationToken(id);
String token = createToken(realm, uri, id, TYPE_REGISTRATION_ACCESS_TOKEN, 0); String token = createToken(session, realm, uri, id, TYPE_REGISTRATION_ACCESS_TOKEN, 0);
return token; return token;
} }
public static String createInitialAccessToken(RealmModel realm, UriInfo uri, ClientInitialAccessModel model) { public static String createInitialAccessToken(KeycloakSession session, RealmModel realm, UriInfo uri, ClientInitialAccessModel model) {
return createToken(realm, uri, model.getId(), TYPE_INITIAL_ACCESS_TOKEN, model.getExpiration() > 0 ? model.getTimestamp() + model.getExpiration() : 0); return createToken(session, realm, uri, model.getId(), TYPE_INITIAL_ACCESS_TOKEN, model.getExpiration() > 0 ? model.getTimestamp() + model.getExpiration() : 0);
} }
public static TokenVerification verifyToken(RealmModel realm, UriInfo uri, String token) { public static TokenVerification verifyToken(KeycloakSession session, RealmModel realm, UriInfo uri, String token) {
if (token == null) { if (token == null) {
return TokenVerification.error(new RuntimeException("Missing token")); return TokenVerification.error(new RuntimeException("Missing token"));
} }
@ -68,7 +70,9 @@ public class ClientRegistrationTokenUtils {
return TokenVerification.error(new RuntimeException("Invalid token", e)); return TokenVerification.error(new RuntimeException("Invalid token", e));
} }
if (!RSAProvider.verify(input, realm.getPublicKey())) { PublicKey publicKey = session.keys().getPublicKey(realm, input.getHeader().getKeyId());
if (!RSAProvider.verify(input, publicKey)) {
return TokenVerification.error(new RuntimeException("Failed verify token")); return TokenVerification.error(new RuntimeException("Failed verify token"));
} }
@ -96,7 +100,7 @@ public class ClientRegistrationTokenUtils {
return TokenVerification.success(jwt); return TokenVerification.success(jwt);
} }
private static String createToken(RealmModel realm, UriInfo uri, String id, String type, int expiration) { private static String createToken(KeycloakSession session, RealmModel realm, UriInfo uri, String id, String type, int expiration) {
JsonWebToken jwt = new JsonWebToken(); JsonWebToken jwt = new JsonWebToken();
String issuer = getIssuer(realm, uri); String issuer = getIssuer(realm, uri);
@ -108,7 +112,9 @@ public class ClientRegistrationTokenUtils {
jwt.issuer(issuer); jwt.issuer(issuer);
jwt.audience(issuer); jwt.audience(issuer);
String token = new JWSBuilder().jsonContent(jwt).rsa256(realm.getPrivateKey()); KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
String token = new JWSBuilder().kid(keys.getKid()).jsonContent(jwt).rsa256(keys.getPrivateKey());
return token; return token;
} }

View file

@ -26,6 +26,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.utils.DefaultKeyProviders;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.ServicesLogger; import org.keycloak.services.ServicesLogger;
@ -83,7 +84,9 @@ public class ApplianceBootstrap {
realm.setSslRequired(SslRequired.EXTERNAL); realm.setSslRequired(SslRequired.EXTERNAL);
realm.setRegistrationAllowed(false); realm.setRegistrationAllowed(false);
realm.setRegistrationEmailAsUsername(false); realm.setRegistrationEmailAsUsername(false);
KeycloakModelUtils.generateRealmKeys(realm);
session.getContext().setRealm(realm);
DefaultKeyProviders.createProviders(realm);
return true; return true;
} }

View file

@ -16,6 +16,7 @@
*/ */
package org.keycloak.services.managers; package org.keycloak.services.managers;
import org.jboss.logging.Logger;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl; import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.RSATokenVerifier; import org.keycloak.RSATokenVerifier;
@ -35,6 +36,7 @@ import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
@ -63,6 +65,7 @@ import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import java.net.URI; import java.net.URI;
import java.security.PublicKey;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -81,7 +84,8 @@ public class AuthenticationManager {
// clientSession note with flag that clientSession was authenticated through SSO cookie // clientSession note with flag that clientSession was authenticated through SSO cookie
public static final String SSO_AUTH = "SSO_AUTH"; public static final String SSO_AUTH = "SSO_AUTH";
protected static ServicesLogger logger = ServicesLogger.ROOT_LOGGER; protected static final Logger logger = Logger.getLogger(AuthenticationManager.class);
public static final String FORM_USERNAME = "username"; public static final String FORM_USERNAME = "username";
// used for auth login // used for auth login
public static final String KEYCLOAK_IDENTITY_COOKIE = "KEYCLOAK_IDENTITY"; public static final String KEYCLOAK_IDENTITY_COOKIE = "KEYCLOAK_IDENTITY";
@ -107,7 +111,13 @@ public class AuthenticationManager {
Cookie cookie = headers.getCookies().get(KEYCLOAK_IDENTITY_COOKIE); Cookie cookie = headers.getCookies().get(KEYCLOAK_IDENTITY_COOKIE);
if (cookie == null) return; if (cookie == null) return;
String tokenString = cookie.getValue(); String tokenString = cookie.getValue();
AccessToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()), false, false);
RSATokenVerifier verifier = RSATokenVerifier.create(tokenString).realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())).checkActive(false).checkTokenType(false);
String kid = verifier.getHeader().getKeyId();
PublicKey publicKey = session.keys().getPublicKey(realm, kid);
AccessToken token = verifier.publicKey(publicKey).verify().getToken();
UserSessionModel cookieSession = session.sessions().getUserSession(realm, token.getSessionState()); UserSessionModel cookieSession = session.sessions().getUserSession(realm, token.getSessionState());
if (cookieSession == null || !cookieSession.getId().equals(userSession.getId())) return; if (cookieSession == null || !cookieSession.getId().equals(userSession.getId())) return;
expireIdentityCookie(realm, uriInfo, connection); expireIdentityCookie(realm, uriInfo, connection);
@ -214,7 +224,7 @@ public class AuthenticationManager {
protocol.backchannelLogout(userSession, clientSession); protocol.backchannelLogout(userSession, clientSession);
clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name()); clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
} catch (Exception e) { } catch (Exception e) {
logger.failedToLogoutClient(e); ServicesLogger.ROOT_LOGGER.failedToLogoutClient(e);
} }
} }
} }
@ -235,7 +245,7 @@ public class AuthenticationManager {
return response; return response;
} }
} catch (Exception e) { } catch (Exception e) {
logger.failedToLogoutClient(e); ServicesLogger.ROOT_LOGGER.failedToLogoutClient(e);
} }
} }
@ -284,7 +294,7 @@ public class AuthenticationManager {
String cookiePath = getIdentityCookiePath(realm, uriInfo); String cookiePath = getIdentityCookiePath(realm, uriInfo);
String issuer = Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()); String issuer = Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName());
AccessToken identityToken = createIdentityToken(realm, user, session, issuer); AccessToken identityToken = createIdentityToken(realm, user, session, issuer);
String encoded = encodeToken(realm, identityToken); String encoded = encodeToken(keycloakSession, realm, identityToken);
boolean secureOnly = realm.getSslRequired().isRequired(connection); boolean secureOnly = realm.getSslRequired().isRequired(connection);
int maxAge = NewCookie.DEFAULT_MAX_AGE; int maxAge = NewCookie.DEFAULT_MAX_AGE;
if (session.isRememberMe()) { if (session.isRememberMe()) {
@ -326,10 +336,15 @@ public class AuthenticationManager {
return null; return null;
} }
protected static String encodeToken(RealmModel realm, Object token) { protected static String encodeToken(KeycloakSession session, RealmModel realm, Object token) {
KeyManager.ActiveKey activeKey = session.keys().getActiveKey(realm);
logger.tracef("Encoding token with kid '%s'", activeKey.getKid());
String encodedToken = new JWSBuilder() String encodedToken = new JWSBuilder()
.kid(activeKey.getKid())
.jsonContent(token) .jsonContent(token)
.rsa256(realm.getPrivateKey()); .rsa256(activeKey.getPrivateKey());
return encodedToken; return encodedToken;
} }
@ -430,7 +445,7 @@ public class AuthenticationManager {
userSession.setNote(AUTH_TIME, String.valueOf(authTime)); userSession.setNote(AUTH_TIME, String.valueOf(authTime));
} }
return protocol.authenticated(userSession, new ClientSessionCode(realm, clientSession)); return protocol.authenticated(userSession, new ClientSessionCode(session, realm, clientSession));
} }
@ -479,7 +494,7 @@ public class AuthenticationManager {
UserConsentModel grantedConsent = session.users().getConsentByClient(realm, user, client.getId()); UserConsentModel grantedConsent = session.users().getConsentByClient(realm, user, client.getId());
ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession); ClientSessionCode accessCode = new ClientSessionCode(session, realm, clientSession);
for (RoleModel r : accessCode.getRequestedRoles()) { for (RoleModel r : accessCode.getRequestedRoles()) {
// Consent already granted by user // Consent already granted by user
@ -535,7 +550,7 @@ public class AuthenticationManager {
List<RoleModel> realmRoles = new LinkedList<>(); List<RoleModel> realmRoles = new LinkedList<>();
MultivaluedMap<String, RoleModel> resourceRoles = new MultivaluedMapImpl<>(); MultivaluedMap<String, RoleModel> resourceRoles = new MultivaluedMapImpl<>();
ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession); ClientSessionCode accessCode = new ClientSessionCode(session, realm, clientSession);
for (RoleModel r : accessCode.getRequestedRoles()) { for (RoleModel r : accessCode.getRequestedRoles()) {
// Consent already granted by user // Consent already granted by user
@ -664,13 +679,21 @@ public class AuthenticationManager {
protected static AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, boolean checkTokenType, protected static AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, boolean checkTokenType,
String tokenString, HttpHeaders headers) { String tokenString, HttpHeaders headers) {
try { try {
AccessToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()), checkActive, checkTokenType); RSATokenVerifier verifier = RSATokenVerifier.create(tokenString).realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())).checkActive(checkActive).checkTokenType(checkTokenType);
String kid = verifier.getHeader().getKeyId();
PublicKey publicKey = session.keys().getPublicKey(realm, kid);
if (publicKey == null) {
logger.debugf("Identity cookie signed with unknown kid '%s'", kid);
return null;
}
verifier.publicKey(publicKey);
AccessToken token = verifier.verify().getToken();
if (checkActive) { if (checkActive) {
if (!token.isActive() || token.getIssuedAt() < realm.getNotBefore()) { if (!token.isActive() || token.getIssuedAt() < realm.getNotBefore()) {
logger.debug("identity cookie expired"); logger.debug("Identity cookie expired");
return null; return null;
} else {
logger.debugv("token active - active: {0}, issued-at: {1}, not-before: {2}", token.isActive(), token.getIssuedAt(), realm.getNotBefore());
} }
} }
@ -689,7 +712,7 @@ public class AuthenticationManager {
return new AuthResult(user, userSession, token); return new AuthResult(user, userSession, token);
} catch (VerificationException e) { } catch (VerificationException e) {
logger.debug("Failed to verify identity token", e); logger.debugf("Failed to verify identity token: %s", e.getMessage());
} }
return null; return null;
} }

View file

@ -244,7 +244,7 @@ public class ResourceAdminManager {
protected boolean sendLogoutRequest(RealmModel realm, ClientModel resource, List<String> adapterSessionIds, List<String> userSessions, int notBefore, String managementUrl) { protected boolean sendLogoutRequest(RealmModel realm, ClientModel resource, List<String> adapterSessionIds, List<String> userSessions, int notBefore, String managementUrl) {
LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getClientId(), adapterSessionIds, notBefore, userSessions); LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getClientId(), adapterSessionIds, notBefore, userSessions);
String token = new TokenManager().encodeToken(realm, adminAction); String token = new TokenManager().encodeToken(session, realm, adminAction);
if (logger.isDebugEnabled()) logger.debugv("logout resource {0} url: {1} sessionIds: " + adapterSessionIds, resource.getClientId(), managementUrl); if (logger.isDebugEnabled()) logger.debugv("logout resource {0} url: {1} sessionIds: " + adapterSessionIds, resource.getClientId(), managementUrl);
URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_LOGOUT).build(); URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_LOGOUT).build();
try { try {
@ -295,7 +295,7 @@ public class ResourceAdminManager {
protected boolean sendPushRevocationPolicyRequest(RealmModel realm, ClientModel resource, int notBefore, String managementUrl) { protected boolean sendPushRevocationPolicyRequest(RealmModel realm, ClientModel resource, int notBefore, String managementUrl) {
PushNotBeforeAction adminAction = new PushNotBeforeAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getClientId(), notBefore); PushNotBeforeAction adminAction = new PushNotBeforeAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getClientId(), notBefore);
String token = new TokenManager().encodeToken(realm, adminAction); String token = new TokenManager().encodeToken(session, realm, adminAction);
logger.debugv("pushRevocation resource: {0} url: {1}", resource.getClientId(), managementUrl); logger.debugv("pushRevocation resource: {0} url: {1}", resource.getClientId(), managementUrl);
URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_PUSH_NOT_BEFORE).build(); URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_PUSH_NOT_BEFORE).build();
try { try {
@ -333,7 +333,7 @@ public class ResourceAdminManager {
protected boolean sendTestNodeAvailabilityRequest(RealmModel realm, ClientModel client, String managementUrl) { protected boolean sendTestNodeAvailabilityRequest(RealmModel realm, ClientModel client, String managementUrl) {
TestAvailabilityAction adminAction = new TestAvailabilityAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, client.getClientId()); TestAvailabilityAction adminAction = new TestAvailabilityAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, client.getClientId());
String token = new TokenManager().encodeToken(realm, adminAction); String token = new TokenManager().encodeToken(session, realm, adminAction);
logger.debugv("testNodes availability resource: {0} url: {1}", client.getClientId(), managementUrl); logger.debugv("testNodes availability resource: {0} url: {1}", client.getClientId(), managementUrl);
URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_TEST_AVAILABLE).build(); URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_TEST_AVAILABLE).build();
try { try {

View file

@ -714,7 +714,7 @@ public class AccountService extends AbstractSecuredLocalService {
try { try {
ClientSessionModel clientSession = auth.getClientSession(); ClientSessionModel clientSession = auth.getClientSession();
ClientSessionCode clientSessionCode = new ClientSessionCode(realm, clientSession); ClientSessionCode clientSessionCode = new ClientSessionCode(session, realm, clientSession);
clientSessionCode.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); clientSessionCode.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
clientSession.setRedirectUri(redirectUri); clientSession.setRedirectUri(redirectUri);
clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, UUID.randomUUID().toString()); clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, UUID.randomUUID().toString());

View file

@ -320,7 +320,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
ctx.saveToClientSession(clientSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE); ctx.saveToClientSession(clientSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
URI redirect = LoginActionsService.firstBrokerLoginProcessor(uriInfo) URI redirect = LoginActionsService.firstBrokerLoginProcessor(uriInfo)
.queryParam(OAuth2Constants.CODE, context.getCode()) .queryParam(OAuth2Constants.CODE, clientCode.getCode())
.build(realmModel.getName()); .build(realmModel.getName());
return Response.status(302).location(redirect).build(); return Response.status(302).location(redirect).build();
@ -333,7 +333,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
updateFederatedIdentity(context, federatedUser); updateFederatedIdentity(context, federatedUser);
clientSession.setAuthenticatedUser(federatedUser); clientSession.setAuthenticatedUser(federatedUser);
return finishOrRedirectToPostBrokerLogin(clientSession, context, false); return finishOrRedirectToPostBrokerLogin(clientSession, context, false, parsedCode.clientSessionCode);
} }
} }
@ -359,7 +359,11 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
if (parsedCode.response != null) { if (parsedCode.response != null) {
return parsedCode.response; return parsedCode.response;
} }
ClientSessionModel clientSession = parsedCode.clientSessionCode.getClientSession(); return afterFirstBrokerLogin(parsedCode.clientSessionCode);
}
private Response afterFirstBrokerLogin(ClientSessionCode clientSessionCode) {
ClientSessionModel clientSession = clientSessionCode.getClientSession();
try { try {
this.event.detail(Details.CODE_ID, clientSession.getId()) this.event.detail(Details.CODE_ID, clientSession.getId())
@ -435,7 +439,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
updateFederatedIdentity(context, federatedUser); updateFederatedIdentity(context, federatedUser);
} }
return finishOrRedirectToPostBrokerLogin(clientSession, context, true); return finishOrRedirectToPostBrokerLogin(clientSession, context, true, clientSessionCode);
} catch (Exception e) { } catch (Exception e) {
return redirectToErrorPage(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e); return redirectToErrorPage(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e);
@ -443,12 +447,12 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
} }
private Response finishOrRedirectToPostBrokerLogin(ClientSessionModel clientSession, BrokeredIdentityContext context, boolean wasFirstBrokerLogin) { private Response finishOrRedirectToPostBrokerLogin(ClientSessionModel clientSession, BrokeredIdentityContext context, boolean wasFirstBrokerLogin, ClientSessionCode clientSessionCode) {
String postBrokerLoginFlowId = context.getIdpConfig().getPostBrokerLoginFlowId(); String postBrokerLoginFlowId = context.getIdpConfig().getPostBrokerLoginFlowId();
if (postBrokerLoginFlowId == null) { if (postBrokerLoginFlowId == null) {
logger.debugf("Skip redirect to postBrokerLogin flow. PostBrokerLogin flow not set for identityProvider '%s'.", context.getIdpConfig().getAlias()); logger.debugf("Skip redirect to postBrokerLogin flow. PostBrokerLogin flow not set for identityProvider '%s'.", context.getIdpConfig().getAlias());
return afterPostBrokerLoginFlowSuccess(clientSession, context, wasFirstBrokerLogin); return afterPostBrokerLoginFlowSuccess(clientSession, context, wasFirstBrokerLogin, clientSessionCode);
} else { } else {
logger.debugf("Redirect to postBrokerLogin flow after authentication with identityProvider '%s'.", context.getIdpConfig().getAlias()); logger.debugf("Redirect to postBrokerLogin flow after authentication with identityProvider '%s'.", context.getIdpConfig().getAlias());
@ -461,7 +465,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
clientSession.setNote(PostBrokerLoginConstants.PBL_AFTER_FIRST_BROKER_LOGIN, String.valueOf(wasFirstBrokerLogin)); clientSession.setNote(PostBrokerLoginConstants.PBL_AFTER_FIRST_BROKER_LOGIN, String.valueOf(wasFirstBrokerLogin));
URI redirect = LoginActionsService.postBrokerLoginProcessor(uriInfo) URI redirect = LoginActionsService.postBrokerLoginProcessor(uriInfo)
.queryParam(OAuth2Constants.CODE, context.getCode()) .queryParam(OAuth2Constants.CODE, clientSessionCode.getCode())
.build(realmModel.getName()); .build(realmModel.getName());
return Response.status(302).location(redirect).build(); return Response.status(302).location(redirect).build();
} }
@ -499,13 +503,13 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
clientSession.removeNote(PostBrokerLoginConstants.PBL_BROKERED_IDENTITY_CONTEXT); clientSession.removeNote(PostBrokerLoginConstants.PBL_BROKERED_IDENTITY_CONTEXT);
clientSession.removeNote(PostBrokerLoginConstants.PBL_AFTER_FIRST_BROKER_LOGIN); clientSession.removeNote(PostBrokerLoginConstants.PBL_AFTER_FIRST_BROKER_LOGIN);
return afterPostBrokerLoginFlowSuccess(clientSession, context, wasFirstBrokerLogin); return afterPostBrokerLoginFlowSuccess(clientSession, context, wasFirstBrokerLogin, parsedCode.clientSessionCode);
} catch (IdentityBrokerException e) { } catch (IdentityBrokerException e) {
return redirectToErrorPage(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e); return redirectToErrorPage(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e);
} }
} }
private Response afterPostBrokerLoginFlowSuccess(ClientSessionModel clientSession, BrokeredIdentityContext context, boolean wasFirstBrokerLogin) { private Response afterPostBrokerLoginFlowSuccess(ClientSessionModel clientSession, BrokeredIdentityContext context, boolean wasFirstBrokerLogin, ClientSessionCode clientSessionCode) {
String providerId = context.getIdpConfig().getAlias(); String providerId = context.getIdpConfig().getAlias();
UserModel federatedUser = clientSession.getAuthenticatedUser(); UserModel federatedUser = clientSession.getAuthenticatedUser();
@ -532,7 +536,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
return redirectToErrorPage(Messages.IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE, federatedUser.getUsername(), linkingUser.getUsername()); return redirectToErrorPage(Messages.IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE, federatedUser.getUsername(), linkingUser.getUsername());
} }
return afterFirstBrokerLogin(context.getCode()); return afterFirstBrokerLogin(clientSessionCode);
} else { } else {
return finishBrokerAuthentication(context, federatedUser, clientSession, providerId); return finishBrokerAuthentication(context, federatedUser, clientSession, providerId);
} }
@ -556,7 +560,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
logger.debugf("Performing local authentication for user [%s].", federatedUser); logger.debugf("Performing local authentication for user [%s].", federatedUser);
} }
return AuthenticationProcessor.redirectToRequiredActions(realmModel, clientSession, uriInfo); return AuthenticationProcessor.redirectToRequiredActions(session, realmModel, clientSession, uriInfo);
} }

View file

@ -619,7 +619,7 @@ public class LoginActionsService {
} }
private Response redirectToAfterBrokerLoginEndpoint(ClientSessionModel clientSession, boolean firstBrokerLogin) { private Response redirectToAfterBrokerLoginEndpoint(ClientSessionModel clientSession, boolean firstBrokerLogin) {
ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession); ClientSessionCode accessCode = new ClientSessionCode(session, realm, clientSession);
clientSession.setTimestamp(Time.currentTime()); clientSession.setTimestamp(Time.currentTime());
URI redirect = firstBrokerLogin ? Urls.identityProviderAfterFirstBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode()) : URI redirect = firstBrokerLogin ? Urls.identityProviderAfterFirstBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode()) :
@ -736,7 +736,7 @@ public class LoginActionsService {
event = event.clone().removeDetail(Details.EMAIL).event(EventType.LOGIN); event = event.clone().removeDetail(Details.EMAIL).event(EventType.LOGIN);
return AuthenticationProcessor.redirectToRequiredActions(realm, clientSession, uriInfo); return AuthenticationProcessor.redirectToRequiredActions(session, realm, clientSession, uriInfo);
} else { } else {
Checks checks = new Checks(); Checks checks = new Checks();
if (!checks.verifyCode(code, ClientSessionModel.Action.REQUIRED_ACTIONS.name(), ClientSessionCode.ActionType.USER)) { if (!checks.verifyCode(code, ClientSessionModel.Action.REQUIRED_ACTIONS.name(), ClientSessionCode.ActionType.USER)) {
@ -779,7 +779,7 @@ public class LoginActionsService {
clientSession.getUserSession().getUser().setEmailVerified(true); clientSession.getUserSession().getUser().setEmailVerified(true);
clientSession.setNote(AuthenticationManager.END_AFTER_REQUIRED_ACTIONS, "true"); clientSession.setNote(AuthenticationManager.END_AFTER_REQUIRED_ACTIONS, "true");
clientSession.setNote(ClientSessionModel.Action.EXECUTE_ACTIONS.name(), "true"); clientSession.setNote(ClientSessionModel.Action.EXECUTE_ACTIONS.name(), "true");
return AuthenticationProcessor.redirectToRequiredActions(realm, clientSession, uriInfo); return AuthenticationProcessor.redirectToRequiredActions(session, realm, clientSession, uriInfo);
} else { } else {
event.error(Errors.INVALID_CODE); event.error(Errors.INVALID_CODE);
return ErrorPage.error(session, Messages.INVALID_CODE); return ErrorPage.error(session, Messages.INVALID_CODE);
@ -886,7 +886,7 @@ public class LoginActionsService {
if (AuthenticationManager.isActionRequired(session, userSession, clientSession, clientConnection, request, uriInfo, event)) { if (AuthenticationManager.isActionRequired(session, userSession, clientSession, clientConnection, request, uriInfo, event)) {
// redirect to a generic code URI so that browser refresh will work // redirect to a generic code URI so that browser refresh will work
return redirectToRequiredActions(code); return redirectToRequiredActions(checks.clientCode.getCode());
} else { } else {
return AuthenticationManager.finishedRequiredActions(session, userSession, clientSession, clientConnection, request, uriInfo, event); return AuthenticationManager.finishedRequiredActions(session, userSession, clientSession, clientConnection, request, uriInfo, event);

View file

@ -19,6 +19,8 @@ package org.keycloak.services.resources;
import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.HttpResponse; import org.jboss.resteasy.spi.HttpResponse;
import org.keycloak.common.util.PemUtils;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.representations.idm.PublishedRealmRepresentation; import org.keycloak.representations.idm.PublishedRealmRepresentation;
@ -52,6 +54,9 @@ public class PublicRealmResource {
@Context @Context
protected HttpResponse response; protected HttpResponse response;
@Context
protected KeycloakSession session;
protected RealmModel realm; protected RealmModel realm;
public PublicRealmResource(RealmModel realm) { public PublicRealmResource(RealmModel realm) {
@ -79,16 +84,16 @@ public class PublicRealmResource {
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public PublishedRealmRepresentation getRealm() { public PublishedRealmRepresentation getRealm() {
Cors.add(request).allowedOrigins(Cors.ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD).auth().build(response); Cors.add(request).allowedOrigins(Cors.ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD).auth().build(response);
return realmRep(realm, uriInfo); return realmRep(session, realm, uriInfo);
} }
public static PublishedRealmRepresentation realmRep(RealmModel realm, UriInfo uriInfo) { public static PublishedRealmRepresentation realmRep(KeycloakSession session, RealmModel realm, UriInfo uriInfo) {
PublishedRealmRepresentation rep = new PublishedRealmRepresentation(); PublishedRealmRepresentation rep = new PublishedRealmRepresentation();
rep.setRealm(realm.getName()); rep.setRealm(realm.getName());
rep.setTokenServiceUrl(OIDCLoginProtocolService.tokenServiceBaseUrl(uriInfo).build(realm.getName()).toString()); rep.setTokenServiceUrl(OIDCLoginProtocolService.tokenServiceBaseUrl(uriInfo).build(realm.getName()).toString());
rep.setAccountServiceUrl(AccountService.accountServiceBaseUrl(uriInfo).build(realm.getName()).toString()); rep.setAccountServiceUrl(AccountService.accountServiceBaseUrl(uriInfo).build(realm.getName()).toString());
rep.setAdminApiUrl(uriInfo.getBaseUriBuilder().path(AdminRoot.class).build().toString()); rep.setAdminApiUrl(uriInfo.getBaseUriBuilder().path(AdminRoot.class).build().toString());
rep.setPublicKeyPem(realm.getPublicKeyPem()); rep.setPublicKeyPem(PemUtils.encodeKey(session.keys().getActiveKey(realm).getPublicKey()));
rep.setNotBefore(realm.getNotBefore()); rep.setNotBefore(realm.getNotBefore());
return rep; return rep;
} }

View file

@ -125,6 +125,7 @@ public class AdminRoot {
if (realm == null) { if (realm == null) {
throw new NotFoundException("Realm not found. Did you type in a bad URL?"); throw new NotFoundException("Realm not found. Did you type in a bad URL?");
} }
session.getContext().setRealm(realm);
return realm; return realm;
} }
@ -170,6 +171,7 @@ public class AdminRoot {
if (realm == null) { if (realm == null) {
throw new UnauthorizedException("Unknown realm in token"); throw new UnauthorizedException("Unknown realm in token");
} }
session.getContext().setRealm(realm);
AuthenticationManager.AuthResult authResult = authManager.authenticateBearerToken(session, realm, uriInfo, clientConnection, headers); AuthenticationManager.AuthResult authResult = authManager.authenticateBearerToken(session, realm, uriInfo, clientConnection, headers);
if (authResult == null) { if (authResult == null) {
logger.debug("Token not valid"); logger.debug("Token not valid");

Some files were not shown because too many files have changed in this diff Show more