KEYCLOAK-905
Realm key rotation for OIDC
This commit is contained in:
parent
1e51952006
commit
d2cae0f8c3
161 changed files with 4905 additions and 2704 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
76
common/src/main/java/org/keycloak/common/util/KeyUtils.java
Normal file
76
common/src/main/java/org/keycloak/common/util/KeyUtils.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -207,4 +207,7 @@ public interface RealmResource {
|
|||
@Path("components")
|
||||
ComponentsResource components();
|
||||
|
||||
@Path("keys")
|
||||
KeyResource keys();
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
104
server-spi/src/main/java/org/keycloak/keys/KeyMetadata.java
Normal file
104
server-spi/src/main/java/org/keycloak/keys/KeyMetadata.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
69
server-spi/src/main/java/org/keycloak/keys/KeyProvider.java
Normal file
69
server-spi/src/main/java/org/keycloak/keys/KeyProvider.java
Normal 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();
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
|
71
server-spi/src/main/java/org/keycloak/models/KeyManager.java
Normal file
71
server-spi/src/main/java/org/keycloak/models/KeyManager.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -145,6 +145,12 @@ public interface KeycloakSession {
|
|||
*/
|
||||
UserFederatedStorageProvider userFederatedStorage();
|
||||
|
||||
/**
|
||||
* Key manager
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
KeyManager keys();
|
||||
|
||||
/**
|
||||
* Keycloak scripting support.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
49
services/src/main/java/org/keycloak/keys/Attributes.java
Normal file
49
services/src/main/java/org/keycloak/keys/Attributes.java
Normal 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);
|
||||
|
||||
}
|
162
services/src/main/java/org/keycloak/keys/DefaultKeyManager.java
Normal file
162
services/src/main/java/org/keycloak/keys/DefaultKeyManager.java
Normal 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());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
56
services/src/main/java/org/keycloak/keys/RsaKeyProvider.java
Normal file
56
services/src/main/java/org/keycloak/keys/RsaKeyProvider.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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('/', '_')
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue