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 {
JWSInput input = new JWSInput(token);
PublicKey publicKey = AdapterRSATokenVerifier.getPublicKey(input, deployment);
PublicKey publicKey = AdapterRSATokenVerifier.getPublicKey(input.getHeader().getKeyId(), deployment);
if (RSAProvider.verify(input, publicKey)) {
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();
PublicKey publicKey = pkLocator.getPublicKey(input, deployment);
PublicKey publicKey = pkLocator.getPublicKey(kid, deployment);
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");
}
@ -51,14 +51,8 @@ public class AdapterRSATokenVerifier {
}
public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment, boolean checkActive, boolean checkTokenType) throws VerificationException {
JWSInput input;
try {
input = new JWSInput(tokenString);
} 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);
RSATokenVerifier verifier = RSATokenVerifier.create(tokenString).realmUrl(deployment.getRealmInfoUrl()).checkActive(checkActive).checkTokenType(checkTokenType);
PublicKey publicKey = getPublicKey(verifier.getHeader().getKeyId(), deployment);
return verifier.publicKey(publicKey).verify().getToken();
}
}

View file

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

View file

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

View file

@ -28,10 +28,10 @@ import java.security.PublicKey;
public interface PublicKeyLocator {
/**
* @param input
* @param kid
* @param deployment
* @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
*/
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 {
X500Name subjectDN = new X500Name("CN=" + subject);
BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis());
Date validityStartDate = new Date(System.currentTimeMillis() - 100000);
Calendar calendar = Calendar.getInstance();
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;
import org.bouncycastle.openssl.PEMWriter;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
/**
@ -33,6 +36,7 @@ import java.security.cert.X509Certificate;
* @version $Revision: 1 $
*/
public final class PemUtils {
static {
BouncyIntegration.init();
}
@ -40,73 +44,112 @@ public final class PemUtils {
private PemUtils() {
}
public static X509Certificate decodeCertificate(InputStream is) throws Exception {
byte[] der = pemToDer(is);
ByteArrayInputStream bis = new ByteArrayInputStream(der);
return DerUtils.decodeCertificate(bis);
/**
* Decode a X509 Certificate from a PEM string
*
* @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);
ByteArrayInputStream bis = new ByteArrayInputStream(der);
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
* @return
* @throws Exception
*/
public static PublicKey decodePublicKey(String pem) throws Exception {
public static PublicKey decodePublicKey(String pem) {
if (pem == null) {
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
* @return
* @throws Exception
*/
public static PrivateKey decodePrivateKey(String pem) throws Exception {
public static PrivateKey decodePrivateKey(String pem) {
if (pem == null) {
return null;
}
try {
byte[] der = pemToDer(pem);
return DerUtils.decodePrivateKey(der);
} catch (Exception e) {
throw new PemException(e);
}
public static PrivateKey decodePrivateKey(InputStream is) throws Exception {
String pem = pemFromStream(is);
return decodePrivateKey(pem);
}
/**
* Decode a PEM file to DER format
* Encode a Key to a PEM string
*
* @param is
* @param key
* @return
* @throws java.io.IOException
* @throws Exception
*/
public static byte[] pemToDer(InputStream is) throws IOException {
String pem = pemFromStream(is);
return pemToDer(pem);
public static String encodeKey(Key key) {
return encode(key);
}
/**
* Decode a PEM string to DER format
* Encode a X509 Certificate to a PEM string
*
* @param pem
* @param certificate
* @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);
return Base64.decode(pem);
}
public static String removeBeginEnd(String pem) {
private static String removeBeginEnd(String pem) {
pem = pem.replaceAll("-----BEGIN (.*)-----", "");
pem = pem.replaceAll("-----END (.*)----", "");
pem = pem.replaceAll("\r\n", "");
@ -114,12 +157,4 @@ public final class PemUtils {
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;
import org.keycloak.common.VerificationException;
import org.keycloak.jose.jws.JWSHeader;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.RSAProvider;
@ -31,84 +32,123 @@ import java.security.PublicKey;
* @version $Revision: 1 $
*/
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 {
AccessToken token = toAccessToken(tokenString, realmKey);
public static RSATokenVerifier create(String tokenString) {
return new RSATokenVerifier(tokenString);
}
tokenVerifications(token, realmUrl, checkActive, checkTokenType);
public static AccessToken verifyToken(String tokenString, PublicKey publicKey, String realmUrl) throws VerificationException {
return RSATokenVerifier.create(tokenString).publicKey(publicKey).realmUrl(realmUrl).verify().getToken();
}
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();
}
public RSATokenVerifier publicKey(PublicKey publicKey) {
this.publicKey = publicKey;
return this;
}
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;
}
private static void tokenVerifications(AccessToken token, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException {
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("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);
throw new VerificationException("Subject missing in token");
}
if (checkTokenType) {
String type = token.getType();
if (type == null || !type.equalsIgnoreCase(TokenUtil.TOKEN_TYPE_BEARER)) {
throw new VerificationException("Token type is incorrect. Expected '" + TokenUtil.TOKEN_TYPE_BEARER + "' but was '" + type + "'");
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()) {
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;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.KeyUtils;
import java.math.BigInteger;
import java.security.Key;
@ -53,7 +54,7 @@ public class JWKBuilder {
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.setKeyType(RSAPublicJWK.RSA);
k.setAlgorithm(RSAPublicJWK.RS256);
@ -64,14 +65,6 @@ public class JWKBuilder {
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
*/

View file

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

View file

@ -27,6 +27,7 @@ public class ConfigPropertyRepresentation {
protected String helpText;
protected String type;
protected Object defaultValue;
protected boolean secret;
public String getName() {
return name;
@ -67,4 +68,12 @@ public class ConfigPropertyRepresentation {
public void setHelpText(String 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.JsonProperty;
import org.bouncycastle.openssl.PEMWriter;
import org.keycloak.common.util.PemUtils;
import java.io.IOException;
import java.io.StringWriter;
import java.security.PublicKey;
/**
@ -85,17 +82,7 @@ public class PublishedRealmRepresentation {
@JsonIgnore
public void setPublicKey(PublicKey publicKey) {
this.publicKey = publicKey;
StringWriter writer = new StringWriter();
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);
this.publicKeyPem = PemUtils.encodeKey(publicKey);
}
public String getTokenServiceUrl() {

View file

@ -72,9 +72,13 @@ public class RealmRepresentation {
protected Integer failureFactor;
//--- end brute force settings
@Deprecated
protected String privateKey;
@Deprecated
protected String publicKey;
@Deprecated
protected String certificate;
@Deprecated
protected String codeSecret;
protected RolesRepresentation roles;
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")
ComponentsResource components();
@Path("keys")
KeyResource keys();
}

View file

@ -60,10 +60,6 @@ public class RealmAdapter implements RealmModel {
protected RealmCacheSession cacheSession;
protected RealmModel updated;
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) {
this.cached = cached;
@ -423,123 +419,6 @@ public class RealmAdapter implements RealmModel {
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
public List<RequiredCredentialModel> getRequiredCredentials() {
if (isUpdated()) return updated.getRequiredCredentials();

View file

@ -89,15 +89,6 @@ public class CachedRealm extends AbstractRevisioned {
protected PasswordPolicy passwordPolicy;
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 accountTheme;
protected String adminTheme;
@ -191,15 +182,6 @@ public class CachedRealm extends AbstractRevisioned {
passwordPolicy = model.getPasswordPolicy();
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();
accountTheme = model.getAccountTheme();
adminTheme = model.getAdminTheme();
@ -415,22 +397,6 @@ public class CachedRealm extends AbstractRevisioned {
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() {
return requiredCredentials;
}
@ -507,10 +473,6 @@ public class CachedRealm extends AbstractRevisioned {
return userFederationMappers;
}
public String getCertificatePem() {
return certificatePem;
}
public List<IdentityProviderModel> getIdentityProviders() {
return identityProviders;
}
@ -591,18 +553,6 @@ public class CachedRealm extends AbstractRevisioned {
return clientTemplates;
}
public PublicKey getPublicKey() {
return publicKey;
}
public PrivateKey getPrivateKey() {
return privateKey;
}
public X509Certificate getCertificate() {
return certificate;
}
public Set<UserFederationMapperModel> getUserFederationMapperSet() {
return userFederationMapperSet;
}

View file

@ -106,7 +106,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
tx.put(sessionCache, id, entity);
ClientSessionAdapter wrap = wrap(realm, entity, false);
wrap.setNote(ClientSessionModel.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
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.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentFactory;
import org.keycloak.component.ComponentModel;
import org.keycloak.jose.jwk.JWKBuilder;
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.UserFederationMapperEntity;
import org.keycloak.models.jpa.entities.UserFederationProviderEntity;
import org.keycloak.models.utils.ComponentUtil;
import org.keycloak.models.utils.KeycloakModelUtils;
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 RealmEntity realm;
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;
private PasswordPolicy passwordPolicy;
private OTPPolicy otpPolicy;
@ -488,106 +486,6 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
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) {
RequiredCredentialModel model = RequiredCredentialModel.BUILT_IN.get(type);
if (model == null) {
@ -2138,6 +2036,13 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
@Override
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();
if (model.getId() == null) {
c.setId(KeycloakModelUtils.generateId());
@ -2171,6 +2076,8 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
@Override
public void updateComponent(ComponentModel component) {
ComponentUtil.getComponentFactory(session, component).validateConfiguration(session, component);
ComponentEntity c = em.find(ComponentEntity.class, component.getId());
if (c == null) return;
c.setName(component.getName());

View file

@ -117,15 +117,6 @@ public class RealmEntity {
@Column(name="NOT_BEFORE")
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")
protected String loginTheme;
@Column(name="ACCOUNT_THEME")
@ -384,30 +375,6 @@ public class RealmEntity {
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() {
return requiredCredentials;
}
@ -567,14 +534,6 @@ public class RealmEntity {
this.attributes = attributes;
}
public String getCertificatePem() {
return certificatePem;
}
public void setCertificatePem(String certificatePem) {
this.certificatePem = certificatePem;
}
public List<IdentityProviderEntity> getIdentityProviders() {
return this.identityProviders;
}

View file

@ -36,6 +36,13 @@
<addColumn tableName="IDENTITY_PROVIDER">
<column name="PROVIDER_DISPLAY_NAME" type="VARCHAR(255)"></column>
</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>

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_8_0;
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 java.util.Date;
@ -57,7 +58,8 @@ public class DefaultMongoUpdaterProvider implements MongoUpdaterProvider {
Update1_4_0.class,
Update1_7_0.class,
Update1_8_0.class,
Update1_9_2.class
Update1_9_2.class,
Update2_3_0.class
};
@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.UserFederationMapperEntity;
import org.keycloak.models.mongo.keycloak.entities.UserFederationProviderEntity;
import org.keycloak.models.utils.ComponentUtil;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.security.Key;
@ -85,11 +86,6 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
private final MongoRealmEntity realm;
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 PasswordPolicy passwordPolicy;
private volatile transient KeycloakSession session;
@ -455,110 +451,6 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
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
public String getLoginTheme() {
return realm.getLoginTheme();
@ -2062,6 +1954,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
@Override
public ComponentModel addComponentModel(ComponentModel model) {
ComponentUtil.getComponentFactory(session, model).validateConfiguration(session, model);
ComponentEntity entity = new ComponentEntity();
if (model.getId() == null) {
entity.setId(KeycloakModelUtils.generateId());
@ -2082,6 +1976,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
@Override
public void updateComponent(ComponentModel model) {
ComponentUtil.getComponentFactory(session, model).validateConfiguration(session, model);
for (ComponentEntity entity : realm.getComponentEntities()) {
if (entity.getId().equals(model.getId())) {
entity.setConfig(model.getConfig());

View file

@ -70,11 +70,6 @@ public class RealmEntity extends AbstractIdentifiableEntity {
private int accessCodeLifespanLogin;
private int notBefore;
private String publicKeyPem;
private String privateKeyPem;
private String certificatePem;
private String codeSecret;
private String loginTheme;
private String accountTheme;
private String adminTheme;
@ -351,30 +346,6 @@ public class RealmEntity extends AbstractIdentifiableEntity {
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() {
return loginTheme;
}
@ -527,14 +498,6 @@ public class RealmEntity extends AbstractIdentifiableEntity {
this.identityProviders = identityProviders;
}
public String getCertificatePem() {
return certificatePem;
}
public void setCertificatePem(String certificatePem) {
this.certificatePem = certificatePem;
}
public boolean isInternationalizationEnabled() {
return internationalizationEnabled;
}

View file

@ -33,6 +33,6 @@ public interface ComponentFactory<CreatedType, ProviderType extends Provider> ex
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 java.io.Serializable;
import java.util.concurrent.ConcurrentHashMap;
/**
* Stored configuration of a User Storage provider instance.
@ -35,6 +36,7 @@ public class ComponentModel implements Serializable {
private String providerType;
private String parentId;
private MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
private transient ConcurrentHashMap<String, Object> notes = new ConcurrentHashMap<>();
public ComponentModel() {}
@ -71,6 +73,57 @@ public class ComponentModel implements Serializable {
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() {
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.
*/
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>
*/
public class PasswordToken {
private String realm;
private String user;
private int timestamp;
public PasswordToken() {
public class KeySpi implements Spi {
@Override
public boolean isInternal() {
return true;
}
public PasswordToken(String realm, String user) {
this.realm = realm;
this.user = user;
this.timestamp = Time.currentTime();
@Override
public String getName() {
return "keys";
}
public String getRealm() {
return realm;
@Override
public Class<? extends Provider> getProviderClass() {
return KeyProvider.class;
}
public void setRealm(String realm) {
this.realm = realm;
@Override
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>
*/
public interface ClientSessionModel {
public static final String ACTION_KEY = "action_key";
public static final String ACTION_SIGNATURE = "action_signature";
public String getId();
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();
/**
* Key manager
*
* @return
*/
KeyManager keys();
/**
* Keycloak scripting support.

View file

@ -178,35 +178,6 @@ public interface RealmModel extends RoleContainerModel {
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();
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;
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.RealmModel;
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>
@ -33,29 +27,6 @@ import org.keycloak.representations.PasswordToken;
*/
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) {
OTPPolicy policy = realm.getOTPPolicy();
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;
import org.bouncycastle.openssl.PEMWriter;
import org.keycloak.broker.social.SocialIdentityProvider;
import org.keycloak.broker.social.SocialIdentityProviderFactory;
import org.keycloak.common.util.Base64Url;
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.component.ComponentModel;
import org.keycloak.keys.KeyProvider;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel;
@ -52,8 +55,6 @@ import javax.crypto.spec.SecretKeySpec;
import javax.transaction.InvalidTransactionException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import java.io.IOException;
import java.io.StringWriter;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
@ -135,82 +136,19 @@ public final class KeycloakModelUtils {
}
public static String getPemFromKey(Key key) {
StringWriter writer = new StringWriter();
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);
return PemUtils.encodeKey(key);
}
public static String getPemFromCertificate(X509Certificate certificate) {
StringWriter writer = new StringWriter();
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);
return PemUtils.encodeCertificate(certificate);
}
public static CertificateRepresentation generateKeyPairCertificate(String subject) {
KeyPair keyPair = null;
try {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048);
keyPair = generator.generateKeyPair();
} 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);
KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
X509Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, subject);
String privateKeyPem = PemUtils.encodeKey(keyPair.getPrivate());
String certPem = PemUtils.encodeCertificate(certificate);
CertificateRepresentation rep = new CertificateRepresentation();
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.ResourceStore;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.Time;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialModel;
@ -265,16 +266,6 @@ public class ModelToRepresentation {
rep.setEnabled(realm.isEnabled());
rep.setNotBefore(realm.getNotBefore());
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.setRegistrationEmailAsUsername(realm.isRegistrationEmailAsUsername());
rep.setRememberMe(realm.isRememberMe());
@ -783,19 +774,38 @@ public class ModelToRepresentation {
propRep.setType(prop.getType());
propRep.setDefaultValue(prop.getDefaultValue());
propRep.setHelpText(prop.getHelpText());
propRep.setSecret(prop.isSecret());
propertiesRep.add(propRep);
}
return propertiesRep;
}
public static ComponentRepresentation toRepresentation(ComponentModel component) {
public static ComponentRepresentation toRepresentation(KeycloakSession session, ComponentModel component, boolean internal) {
ComponentRepresentation rep = new ComponentRepresentation();
rep.setId(component.getId());
rep.setName(component.getName());
rep.setProviderId(component.getProviderId());
rep.setProviderType(component.getProviderType());
rep.setParentId(component.getParentId());
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;
}

View file

@ -31,10 +31,14 @@ import org.keycloak.authorization.store.ScopeStore;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.common.enums.SslRequired;
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.PemUtils;
import org.keycloak.common.util.UriUtils;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialModel;
import org.keycloak.keys.KeyProvider;
import org.keycloak.migration.MigrationProvider;
import org.keycloak.migration.migrators.MigrationUtils;
import org.keycloak.models.AuthenticationExecutionModel;
@ -63,6 +67,7 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.idm.ApplicationRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
@ -99,12 +104,16 @@ import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.util.JsonSerialization;
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.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
@ -183,23 +192,6 @@ public class RepresentationToModel {
if (rep.isVerifyEmail() != null) newRealm.setVerifyEmail(rep.isVerifyEmail());
if (rep.isResetPasswordAllowed() != null) newRealm.setResetPasswordAllowed(rep.isResetPasswordAllowed());
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.getAccountTheme() != null) newRealm.setAccountTheme(rep.getAccountTheme());
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) {
@ -819,20 +818,6 @@ public class RepresentationToModel {
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){
realm.setInternationalizationEnabled(rep.isInternationalizationEnabled());
}
@ -1692,17 +1677,82 @@ public class RepresentationToModel {
return model;
}
public static ComponentModel toModel(ComponentRepresentation rep) {
public static ComponentModel toModel(KeycloakSession session, ComponentRepresentation rep) {
ComponentModel model = new ComponentModel();
model.setParentId(rep.getParentId());
model.setProviderType(rep.getProviderType());
model.setProviderId(rep.getProviderId());
model.setConfig(rep.getConfig());
model.setConfig(new MultivaluedHashMap<>());
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;
}
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) {
if (Boolean.TRUE.equals(clientRepresentation.getAuthorizationServicesEnabled())) {
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 STRING_TYPE="String";
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 LIST_TYPE="List";
public static final String CLIENT_LIST_TYPE="ClientList";
@ -35,6 +36,7 @@ public class ProviderConfigProperty {
protected String helpText;
protected String type;
protected Object defaultValue;
protected boolean secret;
public ProviderConfigProperty() {
}
@ -47,6 +49,11 @@ public class ProviderConfigProperty {
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() {
return name;
}
@ -86,4 +93,13 @@ public class ProviderConfigProperty {
public void setHelpText(String 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;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Base64Url;
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.ClientSessionModel;
import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import javax.crypto.Mac;
import java.security.Key;
import java.security.PublicKey;
import java.security.Signature;
import java.util.HashSet;
import java.util.Set;
@ -38,8 +43,11 @@ import java.util.Set;
*/
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 ClientSessionModel clientSession;
@ -49,32 +57,12 @@ public class ClientSessionCode {
USER
}
public ClientSessionCode(RealmModel realm, ClientSessionModel clientSession) {
public ClientSessionCode(KeycloakSession session, RealmModel realm, ClientSessionModel clientSession) {
this.session = session;
this.realm = realm;
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 {
ClientSessionCode code;
boolean clientSessionNotFound;
@ -114,13 +102,12 @@ public class ClientSessionCode {
return result;
}
String hash = createHash(realm, result.clientSession);
if (!hash.equals(parts[0])) {
if (!verifyCode(code, session, realm, result.clientSession)) {
result.illegalHash = true;
return result;
}
result.code = new ClientSessionCode(realm, result.clientSession);
result.code = new ClientSessionCode(session, realm, result.clientSession);
return result;
} catch (RuntimeException e) {
result.illegalHash = true;
@ -128,8 +115,6 @@ public class ClientSessionCode {
}
}
public static ClientSessionCode parse(String code, KeycloakSession session, RealmModel realm) {
try {
String[] parts = code.split("\\.");
@ -140,12 +125,11 @@ public class ClientSessionCode {
return null;
}
String hash = createHash(realm, clientSession);
if (!hash.equals(parts[0])) {
if (!verifyCode(code, session, realm, clientSession)) {
return null;
}
return new ClientSessionCode(realm, clientSession);
return new ClientSessionCode(session, realm, clientSession);
} catch (RuntimeException e) {
return null;
}
@ -194,7 +178,7 @@ public class ClientSessionCode {
public Set<RoleModel> getRequestedRoles() {
Set<RoleModel> requestedRoles = new HashSet<RoleModel>();
Set<RoleModel> requestedRoles = new HashSet<>();
for (String roleId : clientSession.getRoles()) {
RoleModel role = realm.getRoleById(roleId);
if (role != null) {
@ -205,7 +189,7 @@ public class ClientSessionCode {
}
public Set<ProtocolMapperModel> getRequestedProtocolMappers() {
Set<ProtocolMapperModel> requestedProtocolMappers = new HashSet<ProtocolMapperModel>();
Set<ProtocolMapperModel> requestedProtocolMappers = new HashSet<>();
Set<String> protocolMappers = clientSession.getProtocolMappers();
ClientModel client = clientSession.getClient();
ClientTemplateModel template = client.getClientTemplate();
@ -229,32 +213,67 @@ public class ClientSessionCode {
}
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) {
String hash = createHash(realm, clientSession);
private static String generateCode(KeycloakSession session, RealmModel realm, ClientSessionModel clientSession) {
try {
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
String secret = KeycloakModelUtils.generateSecret();
StringBuilder sb = new StringBuilder();
sb.append(hash);
sb.append(".");
sb.append(secret);
sb.append('.');
sb.append(clientSession.getId());
return sb.toString();
}
String code = sb.toString();
private static String createHash(RealmModel realm, ClientSessionModel clientSession) {
try {
Key codeSecretKey = realm.getCodeSecretKey();
Mac mac = Mac.getInstance(codeSecretKey.getAlgorithm());
mac.init(codeSecretKey);
mac.update(clientSession.getId().getBytes());
mac.update(HASH_SEPERATOR);
mac.update(clientSession.getNote(ClientSessionModel.ACTION_KEY).getBytes());
return Base64Url.encode(mac.doFinal());
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) {
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.CredentialSpi
org.keycloak.keys.PublicKeyStorageSpi
org.keycloak.keys.KeySpi

View file

@ -212,7 +212,7 @@ public class AuthenticationProcessor {
}
public String generateCode() {
ClientSessionCode accessCode = new ClientSessionCode(getRealm(), getClientSession());
ClientSessionCode accessCode = new ClientSessionCode(session, getRealm(), getClientSession());
clientSession.setTimestamp(Time.currentTime());
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
ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
ClientSessionCode accessCode = new ClientSessionCode(session, realm, clientSession);
accessCode.setAction(ClientSessionModel.Action.REQUIRED_ACTIONS.name());
clientSession.setTimestamp(Time.currentTime());
@ -764,7 +764,7 @@ public class AuthenticationProcessor {
}
public void checkClientSession() {
ClientSessionCode code = new ClientSessionCode(realm, clientSession);
ClientSessionCode code = new ClientSessionCode(session, realm, clientSession);
String action = ClientSessionModel.Action.AUTHENTICATE.name();
if (!code.isValidAction(action)) {
throw new AuthenticationFlowException(AuthenticationFlowError.INVALID_CLIENT_SESSION);
@ -862,7 +862,7 @@ public class AuthenticationProcessor {
protected Response authenticationComplete() {
attachSession();
if (isActionRequired()) {
return redirectToRequiredActions(realm, clientSession, uriInfo);
return redirectToRequiredActions(session, realm, clientSession, uriInfo);
} 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
return AuthenticationManager.finishedRequiredActions(session, userSession, clientSession, connection, request, uriInfo, event);

View file

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

View file

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

View file

@ -63,7 +63,7 @@ public class IdentityProviderAuthenticator implements Authenticator {
List<IdentityProviderModel> identityProviders = context.getRealm().getIdentityProviders();
for (IdentityProviderModel identityProvider : identityProviders) {
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(
Urls.identityProviderAuthnRequest(context.getUriInfo().getBaseUri(), providerId, context.getRealm().getName(), accessCode))
.build();

View file

@ -38,6 +38,7 @@ import org.keycloak.authorization.util.Permissions;
import org.keycloak.authorization.util.Tokens;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.AccessToken;
@ -77,6 +78,9 @@ public class AuthorizationTokenService {
@Context
private HttpRequest httpRequest;
@Context
private KeycloakSession session;
public AuthorizationTokenService(AuthorizationProvider authorization) {
this.authorization = authorization;
}
@ -180,7 +184,7 @@ public class AuthorizationTokenService {
String rpt = request.getRpt();
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);
}
@ -252,13 +256,13 @@ public class AuthorizationTokenService {
authorization.setPermissions(permissions);
accessToken.setAuthorization(authorization);
return new TokenManager().encodeToken(getRealm(), accessToken);
return new TokenManager().encodeToken(session, getRealm(), accessToken);
}
private PermissionTicket verifyPermissionTicket(AuthorizationRequest request) {
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);
}

View file

@ -17,6 +17,7 @@
*/
package org.keycloak.authorization.config;
import org.keycloak.common.util.PemUtils;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
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(),
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()),
realm.getPublicKeyPem());
PemUtils.encodeKey(session.keys().getActiveKey(realm).getPublicKey()));
}
@Override

View file

@ -40,6 +40,7 @@ import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.AccessToken;
@ -81,6 +82,9 @@ public class EntitlementService {
@Context
private HttpRequest request;
@Context
private KeycloakSession session;
public EntitlementService(AuthorizationProvider authorization) {
this.authorization = authorization;
}
@ -200,7 +204,7 @@ public class EntitlementService {
authorization.setPermissions(permissions);
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) {
@ -252,7 +256,7 @@ public class EntitlementService {
if (rpt != null && !"".equals(rpt)) {
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);
}

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.store.StoreFactory;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.models.KeyManager;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.services.ErrorResponseException;
@ -130,7 +131,8 @@ public class AbstractPermissionService {
}
private String createPermissionTicket(List<ResourceRepresentation> resources) {
return new JWSBuilder().jsonContent(new PermissionTicket(resources, this.resourceServer.getId(), this.identity.getAccessToken()))
.rsa256(this.authorization.getKeycloakSession().getContext().getRealm().getPrivateKey());
KeyManager.ActiveKey keys = this.authorization.getKeycloakSession().keys().getActiveKey(this.authorization.getRealm());
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.models.KeycloakContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.AccessToken;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.managers.AppAuthManager;
@ -53,10 +54,10 @@ public class Tokens {
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 {
JWSInput jws = new JWSInput(token);
PublicKey publicKey = keycloakSession.keys().getPublicKey(realm, jws.getHeader().getKeyId());
return RSAProvider.verify(jws, publicKey);
} catch (Exception e) {
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.setCode(state);
federatedIdentity.setIdpConfig(getConfig());
federatedIdentity.setIdp(AbstractOAuth2IdentityProvider.this);
federatedIdentity.setCode(state);
return callback.authenticated(federatedIdentity);
}

View file

@ -38,6 +38,7 @@ import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
@ -265,7 +266,8 @@ public class SAMLEndpoint {
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
.relayState(relayState);
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())
.signDocument();
}
@ -291,13 +293,13 @@ public class SAMLEndpoint {
protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState) {
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.STSubType subType = subject.getSubType();
NameIDType subjectNameID = (NameIDType) subType.getBaseID();
//Map<String, String> notes = new HashMap<>();
BrokeredIdentityContext identity = new BrokeredIdentityContext(subjectNameID.getValue());
identity.setCode(relayState);
identity.getContextData().put(SAML_LOGIN_RESPONSE, responseType);
identity.getContextData().put(SAML_ASSERTION, assertion);
@ -340,6 +342,7 @@ public class SAMLEndpoint {
if (authn != null && authn.getSessionIndex() != null) {
identity.setBrokerSessionId(identity.getBrokerUserId() + "." + authn.getSessionIndex());
}
identity.setCode(relayState);
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.IdentityProviderDataMarshaller;
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.AuthnStatementType;
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.models.ClientSessionModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
@ -97,18 +99,9 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
.relayState(request.getState());
if (getConfig().isWantAuthnRequestsSigned()) {
PrivateKey privateKey = realm.getPrivateKey();
PublicKey publicKey = realm.getPublicKey();
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
if (privateKey == null) {
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);
KeyPair keypair = new KeyPair(keys.getPublicKey(), keys.getPrivateKey());
binding.signWith(keypair);
binding.signatureAlgorithm(getSignatureAlgorithm());
@ -155,7 +148,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
String singleLogoutServiceUrl = getConfig().getSingleLogoutServiceUrl();
if (singleLogoutServiceUrl == null || singleLogoutServiceUrl.trim().equals("") || !getConfig().isBackchannelSupported()) return;
SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
JaxrsSAML2BindingBuilder binding = buildLogoutBinding(userSession, realm);
JaxrsSAML2BindingBuilder binding = buildLogoutBinding(session, userSession, realm);
try {
int status = SimpleHttp.doPost(singleLogoutServiceUrl)
.param(GeneralConstants.SAML_REQUEST_KEY, binding.postBinding(logoutBuilder.buildDocument()).encoded())
@ -181,7 +174,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
} else {
try {
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);
} catch (Exception e) {
throw new RuntimeException(e);
@ -200,11 +193,12 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
return logoutBuilder;
}
private JaxrsSAML2BindingBuilder buildLogoutBinding(UserSessionModel userSession, RealmModel realm) {
private JaxrsSAML2BindingBuilder buildLogoutBinding(KeycloakSession session, UserSessionModel userSession, RealmModel realm) {
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
.relayState(userSession.getId());
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())
.signDocument();
}
@ -231,7 +225,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
boolean wantAuthnRequestsSigned = getConfig().isWantAuthnRequestsSigned();
String entityId = getEntityId(uriInfo, realm);
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);
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 org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jwk.JWKBuilder;
import org.keycloak.keys.PublicKeyLoader;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
@ -69,7 +69,7 @@ public class ClientPublicKeyLoader implements PublicKeyLoader {
PublicKey publicKey = getSignatureValidationKey(certInfo);
// 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);
} catch (ModelException me) {
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 org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.PemUtils;
import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jwk.JWKBuilder;
import org.keycloak.keys.PublicKeyLoader;
import org.keycloak.models.KeycloakSession;
import org.keycloak.protocol.oidc.utils.JWKSHttpUtils;
@ -60,7 +60,7 @@ public class OIDCIdentityProviderPublicKeyLoader implements PublicKeyLoader {
return Collections.emptyMap();
}
String kid = JWKBuilder.createKeyId(publicKey);
String kid = KeyUtils.createKeyId(publicKey);
return Collections.singletonMap(kid, publicKey);
} catch (Exception e) {
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);
} else {
try {
RestartLoginCookie.setRestartCookie(realm, clientConnection, uriInfo, clientSession);
RestartLoginCookie.setRestartCookie(session, realm, clientConnection, uriInfo, clientSession);
if (redirectToAuthentication) {
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.JWSInput;
import org.keycloak.jose.jws.crypto.HMACProvider;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.services.ServicesLogger;
@ -33,6 +35,7 @@ import org.keycloak.services.util.CookieHelper;
import javax.crypto.SecretKey;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.UriInfo;
import java.security.PublicKey;
import java.util.HashMap;
import java.util.Map;
@ -112,11 +115,12 @@ public class RestartLoginCookie {
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();
return builder.jsonContent(this)
.hmac256((SecretKey)realm.getCodeSecretKey());
//.rsa256(realm.getPrivateKey());
return builder.kid(keys.getKid()).jsonContent(this)
.rsa256(keys.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);
String encoded = restart.encode(realm);
int keySize = realm.getCodeSecret().length();
int size = encoded.length();
String encoded = restart.encode(session, realm);
String path = AuthenticationManager.getRealmCookiePath(realm, uriInfo);
boolean secureOnly = realm.getSslRequired().isRequired(connection);
CookieHelper.addCookie(KC_RESTART, encoded, path, null, null, -1, secureOnly, true);
@ -157,13 +159,8 @@ public class RestartLoginCookie {
}
String encodedCookie = cook.getValue();
JWSInput input = new JWSInput(encodedCookie);
/*
if (!RSAProvider.verify(input, realm.getPublicKey())) {
logger.debug("Failed to verify encoded RestartLoginCookie");
return null;
}
*/
if (!HMACProvider.verify(input, (SecretKey)realm.getCodeSecretKey())) {
PublicKey publicKey = session.keys().getPublicKey(realm, input.getHeader().getKeyId());
if (!RSAProvider.verify(input, publicKey)) {
logger.debug("Failed to verify encoded RestartLoginCookie");
return null;
}

View file

@ -24,10 +24,12 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.AccessToken;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.Urls;
import org.keycloak.util.JsonSerialization;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.security.PublicKey;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -46,13 +48,33 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi
public Response introspect(String token) {
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();
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.put("client_id", toIntrospect.getIssuedFor());
tokenMetadata.put("username", toIntrospect.getPreferredUsername());
@ -60,7 +82,7 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi
tokenMetadata = JsonSerialization.createObjectNode();
}
tokenMetadata.put("active", active);
tokenMetadata.put("active", valid);
return Response.ok(JsonSerialization.writeValueAsBytes(tokenMetadata)).type(MediaType.APPLICATION_JSON_TYPE).build();
} catch (Exception e) {
@ -70,7 +92,13 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi
protected AccessToken toAccessToken(String token) {
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) {
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.spi.ResteasyProviderFactory;
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.JWKBuilder;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.keys.KeyMetadata;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
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.TokenEndpoint;
import org.keycloak.protocol.oidc.endpoints.UserInfoEndpoint;
import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.services.ServicesLogger;
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.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.util.List;
/**
* Resource class for the oauth/openid connect token service
@ -174,8 +176,16 @@ public class OIDCLoginProtocolService {
@Produces(MediaType.APPLICATION_JSON)
@NoCache
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();
keySet.setKeys(new JWK[]{JWKBuilder.create().rs256(realm.getPublicKey())});
keySet.setKeys(keys);
return keySet;
}

View file

@ -17,11 +17,11 @@
package org.keycloak.protocol.oidc;
import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException;
import org.jboss.logging.Logger;
import org.keycloak.cluster.ClusterProvider;
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.Errors;
import org.keycloak.events.EventBuilder;
@ -35,6 +35,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.ProtocolMapperModel;
@ -55,11 +56,11 @@ import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.RefreshToken;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.UserSessionManager;
import org.keycloak.util.TokenUtil;
import org.keycloak.common.util.Time;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
@ -80,7 +81,8 @@ import java.util.Set;
* @version $Revision: 1 $
*/
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
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 {
RefreshToken refreshToken = verifyRefreshToken(realm, encodedRefreshToken);
RefreshToken refreshToken = verifyRefreshToken(session, realm, encodedRefreshToken);
event.user(refreshToken.getSubject()).session(refreshToken.getSessionState())
.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()));
}
public RefreshToken verifyRefreshToken(RealmModel realm, String encodedRefreshToken) throws OAuthErrorException {
return verifyRefreshToken(realm, encodedRefreshToken, true);
public RefreshToken verifyRefreshToken(KeycloakSession session, RealmModel realm, String encodedRefreshToken) throws OAuthErrorException {
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 {
RefreshToken refreshToken = toRefreshToken(realm, encodedRefreshToken);
RefreshToken refreshToken = toRefreshToken(session, realm, encodedRefreshToken);
if (checkExpiration) {
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);
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");
}
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 {
JWSInput jws = new JWSInput(encodedIDToken);
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");
}
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,
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();
for (ProtocolMapperModel mapping : mappings) {
@ -513,7 +515,7 @@ public class TokenManager {
public AccessToken transformUserInfoAccessToken(KeycloakSession session, AccessToken token, RealmModel realm, ClientModel client, UserModel user,
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();
for (ProtocolMapperModel mapping : mappings) {
@ -533,7 +535,7 @@ public class TokenManager {
public void transformIDToken(KeycloakSession session, IDToken token, RealmModel realm, ClientModel client, UserModel user,
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();
for (ProtocolMapperModel mapping : mappings) {
@ -618,13 +620,9 @@ public class TokenManager {
}
public String encodeToken(RealmModel realm, Object token) {
String encodedToken = new JWSBuilder()
.type(OAuth2Constants.JWT)
.kid(realm.getKeyId())
.jsonContent(token)
.sign(jwsAlgorithm, realm.getPrivateKey());
return encodedToken;
public String encodeToken(KeycloakSession session, RealmModel realm, Object token) {
KeyManager.ActiveKey activeKey = session.keys().getActiveKey(realm);
return new JWSBuilder().type(JWT).kid(activeKey.getKid()).jsonContent(token).sign(jwsAlgorithm, activeKey.getPrivateKey());
}
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() {
KeyManager.ActiveKey activeKey = session.keys().getActiveKey(realm);
if (accessToken != null) {
event.detail(Details.TOKEN_ID, accessToken.getId());
}
@ -746,7 +746,7 @@ public class TokenManager {
AccessTokenResponse res = new AccessTokenResponse();
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.setTokenType("bearer");
res.setSessionState(accessToken.getSessionState());
@ -764,11 +764,11 @@ public class TokenManager {
}
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);
}
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);
if (refreshToken.getExpiration() != 0) {
res.setRefreshExpiresIn(refreshToken.getExpiration() - Time.currentTime());

View file

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

View file

@ -68,9 +68,6 @@ import java.util.Map;
*/
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 MultivaluedMap<String, String> formParams;
private ClientModel client;
@ -203,34 +200,29 @@ public class TokenEndpoint {
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Missing parameter: " + OAuth2Constants.CODE, Response.Status.BAD_REQUEST);
}
ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
if (accessCode == null) {
ClientSessionCode.ParseResult parseResult = ClientSessionCode.parseResult(code, session, realm);
if (parseResult.isClientSessionNotFound() || parseResult.isIllegalHash()) {
String[] parts = code.split("\\.");
if (parts.length == 2) {
event.detail(Details.CODE_ID, parts[1]);
}
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());
String codeExchanged = clientSession.getNote(CODE_EXCHANGED);
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)) {
if (!parseResult.getCode().isValid(ClientSessionModel.Action.CODE_TO_TOKEN.name(), ClientSessionCode.ActionType.CLIENT)) {
event.error(Errors.INVALID_CODE);
throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Code is expired", Response.Status.BAD_REQUEST);
}
accessCode.setAction(null);
clientSession.setNote(CODE_EXCHANGED, "true");
parseResult.getCode().setAction(null);
UserSessionModel userSession = clientSession.getUserSession();
if (userSession == null) {
@ -275,7 +267,7 @@ public class TokenEndpoint {
updateClientSession(clientSession);
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)
.accessToken(token)

View file

@ -129,7 +129,11 @@ public class UserInfoEndpoint {
AccessToken token = null;
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) {
event.error(Errors.INVALID_TOKEN);
throw new ErrorResponseException(OAuthErrorException.INVALID_TOKEN, "Token invalid: " + e.getMessage(), Response.Status.UNAUTHORIZED);
@ -190,7 +194,7 @@ public class UserInfoEndpoint {
claims.put("aud", audience);
Algorithm signatureAlg = cfg.getUserInfoSignedResponseAlg();
PrivateKey privateKey = realm.getPrivateKey();
PrivateKey privateKey = session.keys().getActiveKey(realm).getPrivateKey();
String signedUserInfo = new JWSBuilder()
.jsonContent(claims)

View file

@ -32,6 +32,7 @@ import org.keycloak.dom.saml.v2.protocol.ResponseType;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
@ -393,19 +394,22 @@ public class SamlProtocol implements LoginProtocol {
JaxrsSAML2BindingBuilder bindingBuilder = new JaxrsSAML2BindingBuilder();
bindingBuilder.relayState(relayState);
KeyManager keyManager = session.keys();
KeyManager.ActiveKey keys = keyManager.getActiveKey(realm);
if (samlClient.requiresRealmSignature()) {
String canonicalization = samlClient.getCanonicalizationMethod();
if (canonicalization != null) {
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()) {
String canonicalization = samlClient.getCanonicalizationMethod();
if (canonicalization != null) {
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()) {
PublicKey publicKey = null;
@ -536,7 +540,8 @@ public class SamlProtocol implements LoginProtocol {
if (canonicalization != null) {
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 {
@ -633,7 +638,8 @@ public class SamlProtocol implements LoginProtocol {
private JaxrsSAML2BindingBuilder createBindingBuilder(SamlClient samlClient) {
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder();
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;
}

View file

@ -21,6 +21,7 @@ import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.common.VerificationException;
import org.keycloak.common.util.PemUtils;
import org.keycloak.common.util.StreamUtil;
import org.keycloak.dom.saml.v2.SAML2Object;
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.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.AuthorizationEndpointBase;
@ -59,6 +62,7 @@ import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
@ -77,6 +81,9 @@ public class SamlService extends AuthorizationEndpointBase {
protected static final Logger logger = Logger.getLogger(SamlService.class);
@Context
protected KeycloakSession session;
public SamlService(RealmModel realm, EventBuilder event) {
super(realm, event);
}
@ -374,7 +381,8 @@ public class SamlService extends AuthorizationEndpointBase {
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(logoutRelayState);
if (samlClient.requiresRealmSignature()) {
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 {
@ -508,18 +516,18 @@ public class SamlService extends AuthorizationEndpointBase {
@Produces(MediaType.APPLICATION_XML)
@NoCache
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");
String template = StreamUtil.readString(is);
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-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.signing.certificate}", realm.getCertificatePem());
template = template.replace("${idp.signing.certificate}", PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate()));
return template;
}

View file

@ -18,6 +18,7 @@
package org.keycloak.protocol.saml.installation;
import org.keycloak.Config;
import org.keycloak.common.util.PemUtils;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@ -43,12 +44,12 @@ public class KeycloakSamlClientInstallation implements ClientInstallationProvide
SamlClient samlClient = new SamlClient(client);
StringBuffer buffer = new StringBuffer();
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");
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(" sslPolicy=\"").append(realm.getSslRequired().name()).append("\"\n");
buffer.append(" logoutPage=\"SPECIFY YOUR LOGOUT PAGE!\">\n");
@ -116,7 +117,7 @@ public class KeycloakSamlClientInstallation implements ClientInstallationProvide
buffer.append(" <Keys>\n");
buffer.append(" <Key signing=\"true\">\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(" </Key>\n");
buffer.append(" </Keys>\n");

View file

@ -41,7 +41,7 @@ public class KeycloakSamlSubsystemInstallation implements ClientInstallationProv
SamlClient samlClient = new SamlClient(client);
StringBuffer buffer = new StringBuffer();
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");
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);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
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 clientDirName = client.getClientId()
.replace('/', '_')

View file

@ -18,6 +18,7 @@
package org.keycloak.protocol.saml.installation;
import org.keycloak.Config;
import org.keycloak.common.util.PemUtils;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@ -37,7 +38,7 @@ import java.net.URI;
* @version $Revision: 1 $
*/
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);
String idpEntityId = RealmsResource.realmBaseUrl(UriBuilder.fromUri(serverBaseUri)).build(realm.getName()).toString();
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:X509Data>\n" +
" <dsig:X509Certificate>\n" +
" " + realm.getCertificatePem() + "\n" +
" " + PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate()) + "\n" +
" </dsig:X509Certificate>\n" +
" </dsig:X509Data>\n" +
" </dsig:KeyInfo>\n" +
@ -87,7 +88,7 @@ public class SamlIDPDescriptorClientInstallation implements ClientInstallationPr
@Override
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();
}

View file

@ -115,7 +115,7 @@ public class RoleListMapper extends AbstractSAMLProtocolMapper implements SAMLRo
List<SamlProtocol.ProtocolMapperProcessor<SAMLRoleNameMapper>> roleNameMappers = new LinkedList<>();
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
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) {
ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());

View file

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

View file

@ -79,7 +79,7 @@ public class ClientRegistrationAuth {
return;
}
ClientRegistrationTokenUtils.TokenVerification tokenVerification = ClientRegistrationTokenUtils.verifyToken(realm, uri, split[1]);
ClientRegistrationTokenUtils.TokenVerification tokenVerification = ClientRegistrationTokenUtils.verifyToken(session, realm, uri, split[1]);
if (tokenVerification.getError() != null) {
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.models.ClientInitialAccessModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
@ -32,6 +33,7 @@ import org.keycloak.services.Urls;
import org.keycloak.util.TokenUtil;
import javax.ws.rs.core.UriInfo;
import java.security.PublicKey;
/**
* @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 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();
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;
}
public static String createInitialAccessToken(RealmModel realm, UriInfo uri, ClientInitialAccessModel model) {
return createToken(realm, uri, model.getId(), TYPE_INITIAL_ACCESS_TOKEN, model.getExpiration() > 0 ? model.getTimestamp() + model.getExpiration() : 0);
public static String createInitialAccessToken(KeycloakSession session, RealmModel realm, UriInfo uri, ClientInitialAccessModel model) {
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) {
return TokenVerification.error(new RuntimeException("Missing token"));
}
@ -68,7 +70,9 @@ public class ClientRegistrationTokenUtils {
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"));
}
@ -96,7 +100,7 @@ public class ClientRegistrationTokenUtils {
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();
String issuer = getIssuer(realm, uri);
@ -108,7 +112,9 @@ public class ClientRegistrationTokenUtils {
jwt.issuer(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;
}

View file

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

View file

@ -16,6 +16,7 @@
*/
package org.keycloak.services.managers;
import org.jboss.logging.Logger;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.RSATokenVerifier;
@ -35,6 +36,7 @@ import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
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.UriInfo;
import java.net.URI;
import java.security.PublicKey;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@ -81,7 +84,8 @@ public class AuthenticationManager {
// clientSession note with flag that clientSession was authenticated through SSO cookie
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";
// used for auth login
public static final String KEYCLOAK_IDENTITY_COOKIE = "KEYCLOAK_IDENTITY";
@ -107,7 +111,13 @@ public class AuthenticationManager {
Cookie cookie = headers.getCookies().get(KEYCLOAK_IDENTITY_COOKIE);
if (cookie == null) return;
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());
if (cookieSession == null || !cookieSession.getId().equals(userSession.getId())) return;
expireIdentityCookie(realm, uriInfo, connection);
@ -214,7 +224,7 @@ public class AuthenticationManager {
protocol.backchannelLogout(userSession, clientSession);
clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
} catch (Exception e) {
logger.failedToLogoutClient(e);
ServicesLogger.ROOT_LOGGER.failedToLogoutClient(e);
}
}
}
@ -235,7 +245,7 @@ public class AuthenticationManager {
return response;
}
} catch (Exception e) {
logger.failedToLogoutClient(e);
ServicesLogger.ROOT_LOGGER.failedToLogoutClient(e);
}
}
@ -284,7 +294,7 @@ public class AuthenticationManager {
String cookiePath = getIdentityCookiePath(realm, uriInfo);
String issuer = Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName());
AccessToken identityToken = createIdentityToken(realm, user, session, issuer);
String encoded = encodeToken(realm, identityToken);
String encoded = encodeToken(keycloakSession, realm, identityToken);
boolean secureOnly = realm.getSslRequired().isRequired(connection);
int maxAge = NewCookie.DEFAULT_MAX_AGE;
if (session.isRememberMe()) {
@ -326,10 +336,15 @@ public class AuthenticationManager {
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()
.kid(activeKey.getKid())
.jsonContent(token)
.rsa256(realm.getPrivateKey());
.rsa256(activeKey.getPrivateKey());
return encodedToken;
}
@ -430,7 +445,7 @@ public class AuthenticationManager {
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());
ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
ClientSessionCode accessCode = new ClientSessionCode(session, realm, clientSession);
for (RoleModel r : accessCode.getRequestedRoles()) {
// Consent already granted by user
@ -535,7 +550,7 @@ public class AuthenticationManager {
List<RoleModel> realmRoles = new LinkedList<>();
MultivaluedMap<String, RoleModel> resourceRoles = new MultivaluedMapImpl<>();
ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
ClientSessionCode accessCode = new ClientSessionCode(session, realm, clientSession);
for (RoleModel r : accessCode.getRequestedRoles()) {
// 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,
String tokenString, HttpHeaders headers) {
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 (!token.isActive() || token.getIssuedAt() < realm.getNotBefore()) {
logger.debug("identity cookie expired");
logger.debug("Identity cookie expired");
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);
} catch (VerificationException e) {
logger.debug("Failed to verify identity token", e);
logger.debugf("Failed to verify identity token: %s", e.getMessage());
}
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) {
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);
URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_LOGOUT).build();
try {
@ -295,7 +295,7 @@ public class ResourceAdminManager {
protected boolean sendPushRevocationPolicyRequest(RealmModel realm, ClientModel resource, int notBefore, String managementUrl) {
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);
URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_PUSH_NOT_BEFORE).build();
try {
@ -333,7 +333,7 @@ public class ResourceAdminManager {
protected boolean sendTestNodeAvailabilityRequest(RealmModel realm, ClientModel client, String managementUrl) {
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);
URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_TEST_AVAILABLE).build();
try {

View file

@ -714,7 +714,7 @@ public class AccountService extends AbstractSecuredLocalService {
try {
ClientSessionModel clientSession = auth.getClientSession();
ClientSessionCode clientSessionCode = new ClientSessionCode(realm, clientSession);
ClientSessionCode clientSessionCode = new ClientSessionCode(session, realm, clientSession);
clientSessionCode.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
clientSession.setRedirectUri(redirectUri);
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);
URI redirect = LoginActionsService.firstBrokerLoginProcessor(uriInfo)
.queryParam(OAuth2Constants.CODE, context.getCode())
.queryParam(OAuth2Constants.CODE, clientCode.getCode())
.build(realmModel.getName());
return Response.status(302).location(redirect).build();
@ -333,7 +333,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
updateFederatedIdentity(context, 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) {
return parsedCode.response;
}
ClientSessionModel clientSession = parsedCode.clientSessionCode.getClientSession();
return afterFirstBrokerLogin(parsedCode.clientSessionCode);
}
private Response afterFirstBrokerLogin(ClientSessionCode clientSessionCode) {
ClientSessionModel clientSession = clientSessionCode.getClientSession();
try {
this.event.detail(Details.CODE_ID, clientSession.getId())
@ -435,7 +439,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
updateFederatedIdentity(context, federatedUser);
}
return finishOrRedirectToPostBrokerLogin(clientSession, context, true);
return finishOrRedirectToPostBrokerLogin(clientSession, context, true, clientSessionCode);
} catch (Exception 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();
if (postBrokerLoginFlowId == null) {
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 {
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));
URI redirect = LoginActionsService.postBrokerLoginProcessor(uriInfo)
.queryParam(OAuth2Constants.CODE, context.getCode())
.queryParam(OAuth2Constants.CODE, clientSessionCode.getCode())
.build(realmModel.getName());
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_AFTER_FIRST_BROKER_LOGIN);
return afterPostBrokerLoginFlowSuccess(clientSession, context, wasFirstBrokerLogin);
return afterPostBrokerLoginFlowSuccess(clientSession, context, wasFirstBrokerLogin, parsedCode.clientSessionCode);
} catch (IdentityBrokerException 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();
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 afterFirstBrokerLogin(context.getCode());
return afterFirstBrokerLogin(clientSessionCode);
} else {
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);
}
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) {
ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
ClientSessionCode accessCode = new ClientSessionCode(session, realm, clientSession);
clientSession.setTimestamp(Time.currentTime());
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);
return AuthenticationProcessor.redirectToRequiredActions(realm, clientSession, uriInfo);
return AuthenticationProcessor.redirectToRequiredActions(session, realm, clientSession, uriInfo);
} else {
Checks checks = new Checks();
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.setNote(AuthenticationManager.END_AFTER_REQUIRED_ACTIONS, "true");
clientSession.setNote(ClientSessionModel.Action.EXECUTE_ACTIONS.name(), "true");
return AuthenticationProcessor.redirectToRequiredActions(realm, clientSession, uriInfo);
return AuthenticationProcessor.redirectToRequiredActions(session, realm, clientSession, uriInfo);
} else {
event.error(Errors.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)) {
// redirect to a generic code URI so that browser refresh will work
return redirectToRequiredActions(code);
return redirectToRequiredActions(checks.clientCode.getCode());
} else {
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.spi.HttpRequest;
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.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.representations.idm.PublishedRealmRepresentation;
@ -52,6 +54,9 @@ public class PublicRealmResource {
@Context
protected HttpResponse response;
@Context
protected KeycloakSession session;
protected RealmModel realm;
public PublicRealmResource(RealmModel realm) {
@ -79,16 +84,16 @@ public class PublicRealmResource {
@Produces(MediaType.APPLICATION_JSON)
public PublishedRealmRepresentation getRealm() {
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();
rep.setRealm(realm.getName());
rep.setTokenServiceUrl(OIDCLoginProtocolService.tokenServiceBaseUrl(uriInfo).build(realm.getName()).toString());
rep.setAccountServiceUrl(AccountService.accountServiceBaseUrl(uriInfo).build(realm.getName()).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());
return rep;
}

View file

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

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