KEYCLOAK-4092 key provider for HMAC signatures

This commit is contained in:
Stian Thorgersen 2016-12-15 11:46:15 +01:00
parent a4cbf130b4
commit f29bb7d501
65 changed files with 1578 additions and 329 deletions

View file

@ -17,6 +17,8 @@
package org.keycloak.common.util; package org.keycloak.common.util;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key; import java.security.Key;
import java.security.KeyFactory; import java.security.KeyFactory;
import java.security.KeyPair; import java.security.KeyPair;
@ -38,6 +40,10 @@ public class KeyUtils {
private KeyUtils() { private KeyUtils() {
} }
public static SecretKey loadSecretKey(String secret) {
return new SecretKeySpec(secret.getBytes(), "HmacSHA256");
}
public static KeyPair generateRsaKeyPair(int keysize) { public static KeyPair generateRsaKeyPair(int keysize) {
try { try {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");

View file

@ -19,11 +19,7 @@ package org.keycloak;
import org.keycloak.common.VerificationException; import org.keycloak.common.VerificationException;
import org.keycloak.jose.jws.JWSHeader; 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;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.util.TokenUtil;
import java.security.PublicKey; import java.security.PublicKey;
@ -33,18 +29,10 @@ import java.security.PublicKey;
*/ */
public class RSATokenVerifier { public class RSATokenVerifier {
private final String tokenString; private TokenVerifier tokenVerifier;
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) { private RSATokenVerifier(String tokenString) {
this.tokenString = tokenString; this.tokenVerifier = TokenVerifier.create(tokenString);
} }
public static RSATokenVerifier create(String tokenString) { public static RSATokenVerifier create(String tokenString) {
@ -60,94 +48,45 @@ public class RSATokenVerifier {
} }
public RSATokenVerifier publicKey(PublicKey publicKey) { public RSATokenVerifier publicKey(PublicKey publicKey) {
this.publicKey = publicKey; tokenVerifier.publicKey(publicKey);
return this; return this;
} }
public RSATokenVerifier realmUrl(String realmUrl) { public RSATokenVerifier realmUrl(String realmUrl) {
this.realmUrl = realmUrl; tokenVerifier.realmUrl(realmUrl);
return this; return this;
} }
public RSATokenVerifier checkTokenType(boolean checkTokenType) { public RSATokenVerifier checkTokenType(boolean checkTokenType) {
this.checkTokenType = checkTokenType; tokenVerifier.checkTokenType(checkTokenType);
return this; return this;
} }
public RSATokenVerifier checkActive(boolean checkActive) { public RSATokenVerifier checkActive(boolean checkActive) {
this.checkActive = checkActive; tokenVerifier.checkActive(checkActive);
return this; return this;
} }
public RSATokenVerifier checkRealmUrl(boolean checkRealmUrl) { public RSATokenVerifier checkRealmUrl(boolean checkRealmUrl) {
this.checkRealmUrl = checkRealmUrl; tokenVerifier.checkRealmUrl(checkRealmUrl);
return this; return this;
} }
public RSATokenVerifier parse() throws VerificationException { public RSATokenVerifier parse() throws VerificationException {
if (jws == null) { tokenVerifier.parse();
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; return this;
} }
public AccessToken getToken() throws VerificationException { public AccessToken getToken() throws VerificationException {
parse(); return tokenVerifier.getToken();
return token;
} }
public JWSHeader getHeader() throws VerificationException { public JWSHeader getHeader() throws VerificationException {
parse(); return tokenVerifier.getHeader();
return jws.getHeader();
} }
public RSATokenVerifier verify() throws VerificationException { public RSATokenVerifier verify() throws VerificationException {
parse(); tokenVerifier.verify();
if (publicKey == null) {
throw new VerificationException("Public key not set");
}
if (checkRealmUrl && realmUrl == null) {
throw new VerificationException("Realm URL not set");
}
if (!RSAProvider.verify(jws, publicKey)) {
throw new VerificationException("Invalid token signature");
}
String user = token.getSubject();
if (user == null) {
throw new VerificationException("Subject missing in token");
}
if (checkRealmUrl && !realmUrl.equals(token.getIssuer())) {
throw new VerificationException("Invalid token issuer. Expected '" + realmUrl + "', but was '" + token.getIssuer() + "'");
}
if (checkTokenType && !TokenUtil.TOKEN_TYPE_BEARER.equalsIgnoreCase(token.getType())) {
throw new VerificationException("Token type is incorrect. Expected '" + TokenUtil.TOKEN_TYPE_BEARER + "' but was '" + token.getType() + "'");
}
if (checkActive && !token.isActive()) {
throw new VerificationException("Token is not active");
}
return this; return this;
} }

View file

@ -0,0 +1,170 @@
/*
* 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;
import org.keycloak.common.VerificationException;
import org.keycloak.jose.jws.Algorithm;
import org.keycloak.jose.jws.AlgorithmType;
import org.keycloak.jose.jws.JWSHeader;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.HMACProvider;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.representations.AccessToken;
import org.keycloak.util.TokenUtil;
import javax.crypto.SecretKey;
import java.security.PublicKey;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class TokenVerifier {
private final String tokenString;
private PublicKey publicKey;
private SecretKey secretKey;
private String realmUrl;
private boolean checkTokenType = true;
private boolean checkActive = true;
private boolean checkRealmUrl = true;
private JWSInput jws;
private AccessToken token;
protected TokenVerifier(String tokenString) {
this.tokenString = tokenString;
}
public static TokenVerifier create(String tokenString) {
return new TokenVerifier(tokenString);
}
public TokenVerifier publicKey(PublicKey publicKey) {
this.publicKey = publicKey;
return this;
}
public TokenVerifier secretKey(SecretKey secretKey) {
this.secretKey = secretKey;
return this;
}
public TokenVerifier realmUrl(String realmUrl) {
this.realmUrl = realmUrl;
return this;
}
public TokenVerifier checkTokenType(boolean checkTokenType) {
this.checkTokenType = checkTokenType;
return this;
}
public TokenVerifier checkActive(boolean checkActive) {
this.checkActive = checkActive;
return this;
}
public TokenVerifier checkRealmUrl(boolean checkRealmUrl) {
this.checkRealmUrl = checkRealmUrl;
return this;
}
public TokenVerifier parse() throws VerificationException {
if (jws == null) {
if (tokenString == null) {
throw new VerificationException("Token not set");
}
try {
jws = new JWSInput(tokenString);
} catch (JWSInputException e) {
throw new VerificationException("Failed to parse JWT", e);
}
try {
token = jws.readJsonContent(AccessToken.class);
} catch (JWSInputException e) {
throw new VerificationException("Failed to read access token from JWT", e);
}
}
return this;
}
public AccessToken getToken() throws VerificationException {
parse();
return token;
}
public JWSHeader getHeader() throws VerificationException {
parse();
return jws.getHeader();
}
public TokenVerifier verify() throws VerificationException {
parse();
if (checkRealmUrl && realmUrl == null) {
throw new VerificationException("Realm URL not set");
}
AlgorithmType algorithmType = getHeader().getAlgorithm().getType();
if (AlgorithmType.RSA.equals(algorithmType)) {
if (publicKey == null) {
throw new VerificationException("Public key not set");
}
if (!RSAProvider.verify(jws, publicKey)) {
throw new VerificationException("Invalid token signature");
}
} else if (AlgorithmType.HMAC.equals(algorithmType)) {
if (secretKey == null) {
throw new VerificationException("Secret key not set");
}
if (!HMACProvider.verify(jws, secretKey)) {
throw new VerificationException("Invalid token signature");
}
} else {
throw new VerificationException("Unknown or unsupported token algorith");
}
String user = token.getSubject();
if (user == null) {
throw new VerificationException("Subject missing in token");
}
if (checkRealmUrl && !realmUrl.equals(token.getIssuer())) {
throw new VerificationException("Invalid token issuer. Expected '" + realmUrl + "', but was '" + token.getIssuer() + "'");
}
if (checkTokenType && !TokenUtil.TOKEN_TYPE_BEARER.equalsIgnoreCase(token.getType())) {
throw new VerificationException("Token type is incorrect. Expected '" + TokenUtil.TOKEN_TYPE_BEARER + "' but was '" + token.getType() + "'");
}
if (checkActive && !token.isActive()) {
throw new VerificationException("Token is not active");
}
return this;
}
}

View file

@ -26,23 +26,30 @@ import org.keycloak.jose.jws.crypto.SignatureProvider;
*/ */
public enum Algorithm { public enum Algorithm {
none(null), none(null, null),
HS256(null), HS256(AlgorithmType.HMAC, null),
HS384(null), HS384(AlgorithmType.HMAC, null),
HS512(null), HS512(AlgorithmType.HMAC, null),
RS256(new RSAProvider()), RS256(AlgorithmType.RSA, new RSAProvider()),
RS384(new RSAProvider()), RS384(AlgorithmType.RSA, new RSAProvider()),
RS512(new RSAProvider()), RS512(AlgorithmType.RSA, new RSAProvider()),
ES256(null), ES256(AlgorithmType.ECDSA, null),
ES384(null), ES384(AlgorithmType.ECDSA, null),
ES512(null) ES512(AlgorithmType.ECDSA, null)
; ;
private AlgorithmType type;
private SignatureProvider provider; private SignatureProvider provider;
Algorithm(SignatureProvider provider) { Algorithm(AlgorithmType type, SignatureProvider provider) {
this.type = type;
this.provider = provider; this.provider = provider;
} }
public AlgorithmType getType() {
return type;
}
public SignatureProvider getProvider() { public SignatureProvider getProvider() {
return provider; return provider;
} }

View file

@ -0,0 +1,30 @@
/*
* 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.jose.jws;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public enum AlgorithmType {
RSA,
HMAC,
ECDSA
}

View file

@ -0,0 +1,52 @@
/*
* 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.jose.jws.AlgorithmType;
import javax.crypto.SecretKey;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface HmacKeyProvider extends KeyProvider<HmacKeyMetadata> {
default AlgorithmType getType() {
return AlgorithmType.HMAC;
}
/**
* Return the active secret key, or <code>null</code> if no active key is available.
*
* @return
*/
SecretKey getSecretKey();
/**
* Return the secret key for the specified kid, or <code>null</code> if the kid is unknown.
*
* @param kid
* @return
*/
SecretKey getSecretKey(String kid);
}

View file

@ -0,0 +1,35 @@
/*
* 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.jose.jws.AlgorithmType;
import java.util.Collections;
import java.util.Map;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface HmacKeyProviderFactory extends KeyProviderFactory {
@Override
default Map<String, Object> getTypeMetadata() {
return Collections.singletonMap("algorithmType", AlgorithmType.HMAC);
}
}

View file

@ -17,6 +17,7 @@
package org.keycloak.keys; package org.keycloak.keys;
import org.keycloak.jose.jws.AlgorithmType;
import org.keycloak.provider.Provider; import org.keycloak.provider.Provider;
import java.security.PrivateKey; import java.security.PrivateKey;
@ -27,7 +28,14 @@ import java.util.List;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public interface KeyProvider extends Provider { public interface KeyProvider<T extends KeyMetadata> extends Provider {
/**
* Returns the algorithm type the keys can be used for
*
* @return
*/
AlgorithmType getType();
/** /**
* Return the KID for the active keypair, or <code>null</code> if no active key is available. * Return the KID for the active keypair, or <code>null</code> if no active key is available.
@ -36,33 +44,10 @@ public interface KeyProvider extends Provider {
*/ */
String getKid(); 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 metadata about all keypairs held by the provider
* @return * @return
*/ */
List<KeyMetadata> getKeyMetadata(); List<T> getKeyMetadata();
} }

View file

@ -0,0 +1,60 @@
/*
* 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.jose.jws.AlgorithmType;
import org.keycloak.provider.Provider;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface RsaKeyProvider extends KeyProvider<RsaKeyMetadata> {
default AlgorithmType getType() {
return AlgorithmType.RSA;
}
/**
* 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);
}

View file

@ -0,0 +1,35 @@
/*
* 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.jose.jws.AlgorithmType;
import java.util.Collections;
import java.util.Map;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface RsaKeyProviderFactory extends KeyProviderFactory {
@Override
default Map<String, Object> getTypeMetadata() {
return Collections.singletonMap("algorithmType", AlgorithmType.RSA);
}
}

View file

@ -31,6 +31,7 @@ import org.keycloak.migration.migrators.MigrateTo2_0_0;
import org.keycloak.migration.migrators.MigrateTo2_1_0; import org.keycloak.migration.migrators.MigrateTo2_1_0;
import org.keycloak.migration.migrators.MigrateTo2_2_0; import org.keycloak.migration.migrators.MigrateTo2_2_0;
import org.keycloak.migration.migrators.MigrateTo2_3_0; import org.keycloak.migration.migrators.MigrateTo2_3_0;
import org.keycloak.migration.migrators.MigrateTo2_5_0;
import org.keycloak.migration.migrators.Migration; import org.keycloak.migration.migrators.Migration;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
@ -55,6 +56,7 @@ public class MigrationModelManager {
new MigrateTo2_1_0(), new MigrateTo2_1_0(),
new MigrateTo2_2_0(), new MigrateTo2_2_0(),
new MigrateTo2_3_0(), new MigrateTo2_3_0(),
new MigrateTo2_5_0(),
}; };
public static void migrate(KeycloakSession session) { public static void migrate(KeycloakSession session) {

View file

@ -0,0 +1,46 @@
/*
* 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.migration.migrators;
import org.keycloak.migration.ModelVersion;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.DefaultKeyProviders;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class MigrateTo2_5_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("2.5.0");
@Override
public void migrate(KeycloakSession session) {
session.realms().getRealms().stream().forEach(
r -> DefaultKeyProviders.createSecretProvider(r)
);
}
@Override
public ModelVersion getVersion() {
return VERSION;
}
}

View file

@ -39,6 +39,22 @@ public class DefaultKeyProviders {
generated.setConfig(config); generated.setConfig(config);
realm.addComponentModel(generated); realm.addComponentModel(generated);
createSecretProvider(realm);
}
public static void createSecretProvider(RealmModel realm) {
ComponentModel generated = new ComponentModel();
generated.setName("hmac-generated");
generated.setParentId(realm.getId());
generated.setProviderId("hmac-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) { public static void createProviders(RealmModel realm, String privateKeyPem, String certificatePem) {
@ -57,6 +73,8 @@ public class DefaultKeyProviders {
rsa.setConfig(config); rsa.setConfig(config);
realm.addComponentModel(rsa); realm.addComponentModel(rsa);
createSecretProvider(realm);
} }
} }

View file

@ -41,6 +41,28 @@ public class ConfigurationValidationHelper {
return checkInt(property.getName(), property.getLabel(), required); return checkInt(property.getName(), property.getLabel(), required);
} }
public ConfigurationValidationHelper checkList(ProviderConfigProperty property, boolean required) throws ComponentValidationException {
checkSingle(property.getName(), property.getLabel(), required);
String value = model.getConfig().getFirst(property.getName());
if (value != null && !property.options.contains(value)) {
StringBuilder options = new StringBuilder();
int i = 1;
for (String o : property.options) {
if (i == property.options.size()) {
options.append(" or ");
} else if (i > 1) {
options.append(", ");
}
options.append(o);
i++;
}
throw new ComponentValidationException("''{0}'' should be {1}", property.label, options.toString());
}
return this;
}
public ConfigurationValidationHelper checkInt(String key, String label, boolean required) throws ComponentValidationException { public ConfigurationValidationHelper checkInt(String key, String label, boolean required) throws ComponentValidationException {
checkSingle(key, label, required); checkSingle(key, label, required);

View file

@ -0,0 +1,25 @@
/*
* 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;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class HmacKeyMetadata extends KeyMetadata {
}

View file

@ -17,22 +17,15 @@
package org.keycloak.keys; package org.keycloak.keys;
import java.security.PublicKey;
import java.security.cert.Certificate;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public class KeyMetadata { public abstract class KeyMetadata {
public enum Status { public enum Status {
ACTIVE, PASSIVE, DISABLED ACTIVE, PASSIVE, DISABLED
} }
public enum Type {
RSA
}
private String providerId; private String providerId;
private long providerPriority; private long providerPriority;
@ -40,11 +33,6 @@ public class KeyMetadata {
private Status status; private Status status;
private Type type;
private PublicKey publicKey;
private Certificate certificate;
public String getProviderId() { public String getProviderId() {
return providerId; return providerId;
} }
@ -77,28 +65,4 @@ public class KeyMetadata {
this.status = status; this.status = status;
} }
public Type getType() {
return type;
}
public void setType(Type type) {
this.type = type;
}
public PublicKey getPublicKey() {
return publicKey;
}
public void setPublicKey(PublicKey publicKey) {
this.publicKey = publicKey;
}
public Certificate getCertificate() {
return certificate;
}
public void setCertificate(Certificate certificate) {
this.certificate = certificate;
}
} }

View file

@ -0,0 +1,47 @@
/*
* 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 RsaKeyMetadata extends KeyMetadata {
private PublicKey publicKey;
private Certificate certificate;
public PublicKey getPublicKey() {
return publicKey;
}
public void setPublicKey(PublicKey publicKey) {
this.publicKey = publicKey;
}
public Certificate getCertificate() {
return certificate;
}
public void setCertificate(Certificate certificate) {
this.certificate = certificate;
}
}

View file

@ -17,8 +17,10 @@
package org.keycloak.models; package org.keycloak.models;
import org.keycloak.keys.KeyMetadata; import org.keycloak.keys.HmacKeyMetadata;
import org.keycloak.keys.RsaKeyMetadata;
import javax.crypto.SecretKey;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.cert.Certificate; import java.security.cert.Certificate;
@ -30,21 +32,27 @@ import java.util.List;
*/ */
public interface KeyManager { public interface KeyManager {
ActiveKey getActiveKey(RealmModel realm); ActiveRsaKey getActiveRsaKey(RealmModel realm);
PublicKey getPublicKey(RealmModel realm, String kid); PublicKey getRsaPublicKey(RealmModel realm, String kid);
Certificate getCertificate(RealmModel realm, String kid); Certificate getRsaCertificate(RealmModel realm, String kid);
List<KeyMetadata> getKeys(RealmModel realm, boolean includeDisabled); List<RsaKeyMetadata> getRsaKeys(RealmModel realm, boolean includeDisabled);
class ActiveKey { ActiveHmacKey getActiveHmacKey(RealmModel realm);
SecretKey getHmacSecretKey(RealmModel realm, String kid);
List<HmacKeyMetadata> getHmacKeys(RealmModel realm, boolean includeDisabled);
class ActiveRsaKey {
private final String kid; private final String kid;
private final PrivateKey privateKey; private final PrivateKey privateKey;
private final PublicKey publicKey; private final PublicKey publicKey;
private final X509Certificate certificate; private final X509Certificate certificate;
public ActiveKey(String kid, PrivateKey privateKey, PublicKey publicKey, X509Certificate certificate) { public ActiveRsaKey(String kid, PrivateKey privateKey, PublicKey publicKey, X509Certificate certificate) {
this.kid = kid; this.kid = kid;
this.privateKey = privateKey; this.privateKey = privateKey;
this.publicKey = publicKey; this.publicKey = publicKey;
@ -68,4 +76,22 @@ public interface KeyManager {
} }
} }
class ActiveHmacKey {
private final String kid;
private final SecretKey secretKey;
public ActiveHmacKey(String kid, SecretKey secretKey) {
this.kid = kid;
this.secretKey = secretKey;
}
public String getKid() {
return kid;
}
public SecretKey getSecretKey() {
return secretKey;
}
}
} }

View file

@ -22,6 +22,6 @@ package org.keycloak.provider;
*/ */
public interface Provider { public interface Provider {
public void close(); void close();
} }

View file

@ -47,7 +47,7 @@ public class UmaWellKnownProvider implements WellKnownProvider {
return Configuration.fromDefault(RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString(), realm.getName(), return Configuration.fromDefault(RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString(), realm.getName(),
URI.create(RealmsResource.protocolUrl(uriInfo).path(OIDCLoginProtocolService.class, "auth").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString()), URI.create(RealmsResource.protocolUrl(uriInfo).path(OIDCLoginProtocolService.class, "auth").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString()),
URI.create(RealmsResource.protocolUrl(uriInfo).path(OIDCLoginProtocolService.class, "token").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString()), URI.create(RealmsResource.protocolUrl(uriInfo).path(OIDCLoginProtocolService.class, "token").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString()),
PemUtils.encodeKey(session.keys().getActiveKey(realm).getPublicKey())); PemUtils.encodeKey(session.keys().getActiveRsaKey(realm).getPublicKey()));
} }
@Override @Override

View file

@ -131,7 +131,7 @@ public class AbstractPermissionService {
} }
private String createPermissionTicket(List<ResourceRepresentation> resources) { private String createPermissionTicket(List<ResourceRepresentation> resources) {
KeyManager.ActiveKey keys = this.authorization.getKeycloakSession().keys().getActiveKey(this.authorization.getRealm()); KeyManager.ActiveRsaKey keys = this.authorization.getKeycloakSession().keys().getActiveRsaKey(this.authorization.getRealm());
return new JWSBuilder().kid(keys.getKid()).jsonContent(new PermissionTicket(resources, this.resourceServer.getId(), this.identity.getAccessToken())) return new JWSBuilder().kid(keys.getKid()).jsonContent(new PermissionTicket(resources, this.resourceServer.getId(), this.identity.getAccessToken()))
.rsa256(keys.getPrivateKey()); .rsa256(keys.getPrivateKey());
} }

View file

@ -57,7 +57,7 @@ public class Tokens {
public static boolean verifySignature(KeycloakSession keycloakSession, RealmModel realm, String token) { public static boolean verifySignature(KeycloakSession keycloakSession, RealmModel realm, String token) {
try { try {
JWSInput jws = new JWSInput(token); JWSInput jws = new JWSInput(token);
PublicKey publicKey = keycloakSession.keys().getPublicKey(realm, jws.getHeader().getKeyId()); PublicKey publicKey = keycloakSession.keys().getRsaPublicKey(realm, jws.getHeader().getKeyId());
return RSAProvider.verify(jws, publicKey); return RSAProvider.verify(jws, publicKey);
} catch (Exception e) { } catch (Exception e) {
throw new ErrorResponseException("invalid_signature", "Unexpected error while validating signature.", Status.INTERNAL_SERVER_ERROR); throw new ErrorResponseException("invalid_signature", "Unexpected error while validating signature.", Status.INTERNAL_SERVER_ERROR);

View file

@ -301,7 +301,7 @@ public class SAMLEndpoint {
.relayState(relayState); .relayState(relayState);
boolean postBinding = config.isPostBindingResponse(); boolean postBinding = config.isPostBindingResponse();
if (config.isWantAuthnRequestsSigned()) { if (config.isWantAuthnRequestsSigned()) {
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm); KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
String keyName = config.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate()); String keyName = config.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate());
binding.signWith(keyName, keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()) binding.signWith(keyName, keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate())
.signatureAlgorithm(provider.getSignatureAlgorithm()) .signatureAlgorithm(provider.getSignatureAlgorithm())
@ -332,7 +332,7 @@ public class SAMLEndpoint {
protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState, String clientId) { protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState, String clientId) {
try { try {
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm); KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
AssertionType assertion = AssertionUtil.getAssertion(responseType, keys.getPrivateKey()); AssertionType assertion = AssertionUtil.getAssertion(responseType, keys.getPrivateKey());
SubjectType subject = assertion.getSubject(); SubjectType subject = assertion.getSubject();
SubjectType.STSubType subType = subject.getSubType(); SubjectType.STSubType subType = subject.getSubType();

View file

@ -30,6 +30,7 @@ import org.keycloak.dom.saml.v2.assertion.NameIDType;
import org.keycloak.dom.saml.v2.assertion.SubjectType; import org.keycloak.dom.saml.v2.assertion.SubjectType;
import org.keycloak.dom.saml.v2.protocol.ResponseType; import org.keycloak.dom.saml.v2.protocol.ResponseType;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
import org.keycloak.keys.RsaKeyMetadata;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeyManager; import org.keycloak.models.KeyManager;
@ -103,7 +104,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
boolean postBinding = getConfig().isPostBindingAuthnRequest(); boolean postBinding = getConfig().isPostBindingAuthnRequest();
if (getConfig().isWantAuthnRequestsSigned()) { if (getConfig().isWantAuthnRequestsSigned()) {
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm); KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
KeyPair keypair = new KeyPair(keys.getPublicKey(), keys.getPrivateKey()); KeyPair keypair = new KeyPair(keys.getPublicKey(), keys.getPrivateKey());
@ -205,7 +206,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder() JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
.relayState(userSession.getId()); .relayState(userSession.getId());
if (getConfig().isWantAuthnRequestsSigned()) { if (getConfig().isWantAuthnRequestsSigned()) {
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm); KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
String keyName = getConfig().getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate()); String keyName = getConfig().getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate());
binding.signWith(keyName, keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()) binding.signWith(keyName, keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate())
.signatureAlgorithm(getSignatureAlgorithm()) .signatureAlgorithm(getSignatureAlgorithm())
@ -236,18 +237,18 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat(); String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
StringBuilder keysString = new StringBuilder(); StringBuilder keysString = new StringBuilder();
Set<KeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list Set<RsaKeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list
? (int) (o2.getProviderPriority() - o1.getProviderPriority()) ? (int) (o2.getProviderPriority() - o1.getProviderPriority())
: (o1.getStatus() == KeyMetadata.Status.PASSIVE ? 1 : -1)); : (o1.getStatus() == KeyMetadata.Status.PASSIVE ? 1 : -1));
keys.addAll(session.keys().getKeys(realm, false)); keys.addAll(session.keys().getRsaKeys(realm, false));
for (KeyMetadata key : keys) { for (RsaKeyMetadata key : keys) {
addKeyInfo(keysString, key, KeyTypes.SIGNING.value()); addKeyInfo(keysString, key, KeyTypes.SIGNING.value());
} }
String descriptor = SPMetadataDescriptor.getSPDescriptor(authnBinding, endpoint, endpoint, wantAuthnRequestsSigned, entityId, nameIDPolicyFormat, keysString.toString()); String descriptor = SPMetadataDescriptor.getSPDescriptor(authnBinding, endpoint, endpoint, wantAuthnRequestsSigned, entityId, nameIDPolicyFormat, keysString.toString());
return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build(); return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
} }
private static void addKeyInfo(StringBuilder target, KeyMetadata key, String purpose) { private static void addKeyInfo(StringBuilder target, RsaKeyMetadata key, String purpose) {
if (key == null) { if (key == null) {
return; return;
} }

View file

@ -0,0 +1,47 @@
/*
* 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.models.RealmModel;
import org.keycloak.provider.ConfigurationValidationHelper;
import org.keycloak.provider.ProviderConfigurationBuilder;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public abstract class AbstractHmacKeyProviderFactory implements HmacKeyProviderFactory {
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, RealmModel realm, ComponentModel model) throws ComponentValidationException {
ConfigurationValidationHelper.check(model)
.checkLong(Attributes.PRIORITY_PROPERTY, false)
.checkBoolean(Attributes.ENABLED_PROPERTY, false)
.checkBoolean(Attributes.ACTIVE_PROPERTY, false);
}
}

View file

@ -18,6 +18,7 @@
package org.keycloak.keys; package org.keycloak.keys;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
import org.keycloak.jose.jws.AlgorithmType;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import java.security.KeyPair; import java.security.KeyPair;
@ -30,7 +31,7 @@ import java.util.List;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public abstract class AbstractRsaKeyProvider implements KeyProvider { public abstract class AbstractRsaKeyProvider implements RsaKeyProvider {
private final boolean enabled; private final boolean enabled;
@ -77,11 +78,11 @@ public abstract class AbstractRsaKeyProvider implements KeyProvider {
} }
@Override @Override
public final List<KeyMetadata> getKeyMetadata() { public final List<RsaKeyMetadata> getKeyMetadata() {
String kid = keys.getKid(); String kid = keys.getKid();
PublicKey publicKey = keys.getKeyPair().getPublic(); PublicKey publicKey = keys.getKeyPair().getPublic();
if (kid != null && publicKey != null) { if (kid != null && publicKey != null) {
KeyMetadata k = new KeyMetadata(); RsaKeyMetadata k = new RsaKeyMetadata();
k.setProviderId(model.getId()); k.setProviderId(model.getId());
k.setProviderPriority(model.get(Attributes.PRIORITY_KEY, 0l)); k.setProviderPriority(model.get(Attributes.PRIORITY_KEY, 0l));
k.setKid(kid); k.setKid(kid);
@ -92,7 +93,6 @@ public abstract class AbstractRsaKeyProvider implements KeyProvider {
} else { } else {
k.setStatus(KeyMetadata.Status.DISABLED); k.setStatus(KeyMetadata.Status.DISABLED);
} }
k.setType(KeyMetadata.Type.RSA);
k.setPublicKey(publicKey); k.setPublicKey(publicKey);
k.setCertificate(keys.getCertificate()); k.setCertificate(keys.getCertificate());
return Collections.singletonList(k); return Collections.singletonList(k);

View file

@ -27,7 +27,7 @@ import org.keycloak.provider.ProviderConfigurationBuilder;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public abstract class AbstractRsaKeyProviderFactory implements KeyProviderFactory { public abstract class AbstractRsaKeyProviderFactory implements RsaKeyProviderFactory {
public final static ProviderConfigurationBuilder configurationBuilder() { public final static ProviderConfigurationBuilder configurationBuilder() {
return ProviderConfigurationBuilder.create() return ProviderConfigurationBuilder.create()

View file

@ -44,6 +44,13 @@ public interface Attributes {
ProviderConfigProperty CERTIFICATE_PROPERTY = new ProviderConfigProperty(CERTIFICATE_KEY, "X509 Certificate", "X509 Certificate encoded in PEM format", FILE_TYPE, null); ProviderConfigProperty CERTIFICATE_PROPERTY = new ProviderConfigProperty(CERTIFICATE_KEY, "X509 Certificate", "X509 Certificate encoded in PEM format", FILE_TYPE, null);
String KEY_SIZE_KEY = "keySize"; String KEY_SIZE_KEY = "keySize";
ProviderConfigProperty KEY_SIZE_PROPERTY = new ProviderConfigProperty(KEY_SIZE_KEY, "Keysize", "Size for the generated keys (1024, 2048 or 4096)", LIST_TYPE, "2048", "1024", "2048", "4096"); ProviderConfigProperty KEY_SIZE_PROPERTY = new ProviderConfigProperty(KEY_SIZE_KEY, "Key size", "Size for the generated keys", LIST_TYPE, "2048", "1024", "2048", "4096");
String KID_KEY = "kid";
String SECRET_KEY = "secret";
String SECRET_SIZE_KEY = "secretSize";
ProviderConfigProperty SECRET_SIZE_PROPERTY = new ProviderConfigProperty(SECRET_SIZE_KEY, "Secret size", "Size in bytes for the generated secret", LIST_TYPE, "32", "32", "64", "128", "256", "512");
} }

View file

@ -19,11 +19,13 @@ package org.keycloak.keys;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
import org.keycloak.jose.jws.AlgorithmType;
import org.keycloak.models.KeyManager; import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.ProviderFactory;
import javax.crypto.SecretKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.util.Comparator; import java.util.Comparator;
@ -47,28 +49,50 @@ public class DefaultKeyManager implements KeyManager {
} }
@Override @Override
public ActiveKey getActiveKey(RealmModel realm) { public ActiveRsaKey getActiveRsaKey(RealmModel realm) {
for (KeyProvider p : getProviders(realm)) { for (KeyProvider p : getProviders(realm)) {
if (p.getKid() != null && p.getPrivateKey() != null) { if (p.getType().equals(AlgorithmType.RSA)) {
RsaKeyProvider r = (RsaKeyProvider) p;
if (r.getKid() != null && r.getPrivateKey() != null) {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.tracev("Active key realm={0} kid={1}", realm.getName(), p.getKid()); logger.tracev("Active key realm={0} kid={1}", realm.getName(), p.getKid());
} }
String kid = p.getKid(); String kid = p.getKid();
return new ActiveKey(kid, p.getPrivateKey(), p.getPublicKey(kid), p.getCertificate(kid)); return new ActiveRsaKey(kid, r.getPrivateKey(), r.getPublicKey(kid), r.getCertificate(kid));
}
}
}
throw new RuntimeException("Failed to get RSA keys");
}
@Override
public ActiveHmacKey getActiveHmacKey(RealmModel realm) {
for (KeyProvider p : getProviders(realm)) {
if (p.getType().equals(AlgorithmType.HMAC)) {
HmacKeyProvider h = (HmacKeyProvider) p;
if (h.getKid() != null && h.getSecretKey() != null) {
if (logger.isTraceEnabled()) {
logger.tracev("Active secret realm={0} kid={1}", realm.getName(), p.getKid());
}
String kid = p.getKid();
return new ActiveHmacKey(kid, h.getSecretKey());
}
} }
} }
throw new RuntimeException("Failed to get keys"); throw new RuntimeException("Failed to get keys");
} }
@Override @Override
public PublicKey getPublicKey(RealmModel realm, String kid) { public PublicKey getRsaPublicKey(RealmModel realm, String kid) {
if (kid == null) { if (kid == null) {
logger.warnv("KID is null, can't find public key", realm.getName(), kid); logger.warnv("KID is null, can't find public key", realm.getName(), kid);
return null; return null;
} }
for (KeyProvider p : getProviders(realm)) { for (KeyProvider p : getProviders(realm)) {
PublicKey publicKey = p.getPublicKey(kid); if (p.getType().equals(AlgorithmType.RSA)) {
RsaKeyProvider r = (RsaKeyProvider) p;
PublicKey publicKey = r.getPublicKey(kid);
if (publicKey != null) { if (publicKey != null) {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.tracev("Found public key realm={0} kid={1}", realm.getName(), kid); logger.tracev("Found public key realm={0} kid={1}", realm.getName(), kid);
@ -76,6 +100,7 @@ public class DefaultKeyManager implements KeyManager {
return publicKey; return publicKey;
} }
} }
}
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.tracev("Failed to find public key realm={0} kid={1}", realm.getName(), kid); logger.tracev("Failed to find public key realm={0} kid={1}", realm.getName(), kid);
} }
@ -83,14 +108,16 @@ public class DefaultKeyManager implements KeyManager {
} }
@Override @Override
public Certificate getCertificate(RealmModel realm, String kid) { public Certificate getRsaCertificate(RealmModel realm, String kid) {
if (kid == null) { if (kid == null) {
logger.warnv("KID is null, can't find public key", realm.getName(), kid); logger.warnv("KID is null, can't find public key", realm.getName(), kid);
return null; return null;
} }
for (KeyProvider p : getProviders(realm)) { for (KeyProvider p : getProviders(realm)) {
Certificate certificate = p.getCertificate(kid); if (p.getType().equals(AlgorithmType.RSA)) {
RsaKeyProvider r = (RsaKeyProvider) p;
Certificate certificate = r.getCertificate(kid);
if (certificate != null) { if (certificate != null) {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.tracev("Found certificate realm={0} kid={1}", realm.getName(), kid); logger.tracev("Found certificate realm={0} kid={1}", realm.getName(), kid);
@ -98,6 +125,7 @@ public class DefaultKeyManager implements KeyManager {
return certificate; return certificate;
} }
} }
}
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.tracev("Failed to find certificate realm={0} kid={1}", realm.getName(), kid); logger.tracev("Failed to find certificate realm={0} kid={1}", realm.getName(), kid);
} }
@ -105,20 +133,63 @@ public class DefaultKeyManager implements KeyManager {
} }
@Override @Override
public List<KeyMetadata> getKeys(RealmModel realm, boolean includeDisabled) { public SecretKey getHmacSecretKey(RealmModel realm, String kid) {
List<KeyMetadata> keys = new LinkedList<>(); if (kid == null) {
logger.warnv("KID is null, can't find public key", realm.getName(), kid);
return null;
}
for (KeyProvider p : getProviders(realm)) { for (KeyProvider p : getProviders(realm)) {
if (p.getType().equals(AlgorithmType.HMAC)) {
HmacKeyProvider h = (HmacKeyProvider) p;
SecretKey s = h.getSecretKey(kid);
if (s != null) {
if (logger.isTraceEnabled()) {
logger.tracev("Found secret key realm={0} kid={1}", realm.getName(), kid);
}
return s;
}
}
}
if (logger.isTraceEnabled()) {
logger.tracev("Failed to find secret key realm={0} kid={1}", realm.getName(), kid);
}
return null;
}
@Override
public List<RsaKeyMetadata> getRsaKeys(RealmModel realm, boolean includeDisabled) {
List<RsaKeyMetadata> keys = new LinkedList<>();
for (KeyProvider p : getProviders(realm)) {
if (p instanceof RsaKeyProvider) {
if (includeDisabled) { if (includeDisabled) {
keys.addAll(p.getKeyMetadata()); keys.addAll(p.getKeyMetadata());
} else { } else {
p.getKeyMetadata().stream().filter(k -> k.getStatus() != KeyMetadata.Status.DISABLED).forEach(k -> keys.add(k)); List<RsaKeyMetadata> metadata = p.getKeyMetadata();
metadata.stream().filter(k -> k.getStatus() != KeyMetadata.Status.DISABLED).forEach(k -> keys.add(k));
}
}
}
return keys;
}
@Override
public List<HmacKeyMetadata> getHmacKeys(RealmModel realm, boolean includeDisabled) {
List<HmacKeyMetadata> keys = new LinkedList<>();
for (KeyProvider p : getProviders(realm)) {
if (p instanceof HmacKeyProvider) {
if (includeDisabled) {
keys.addAll(p.getKeyMetadata());
} else {
List<HmacKeyMetadata> metadata = p.getKeyMetadata();
metadata.stream().filter(k -> k.getStatus() != KeyMetadata.Status.DISABLED).forEach(k -> keys.add(k));
}
} }
} }
return keys; return keys;
} }
private List<KeyProvider> getProviders(RealmModel realm) { private List<KeyProvider> getProviders(RealmModel realm) {
boolean active = false;
List<KeyProvider> providers = providersMap.get(realm.getId()); List<KeyProvider> providers = providersMap.get(realm.getId());
if (providers == null) { if (providers == null) {
providers = new LinkedList<>(); providers = new LinkedList<>();
@ -126,6 +197,9 @@ public class DefaultKeyManager implements KeyManager {
List<ComponentModel> components = new LinkedList<>(realm.getComponents(realm.getId(), KeyProvider.class.getName())); List<ComponentModel> components = new LinkedList<>(realm.getComponents(realm.getId(), KeyProvider.class.getName()));
components.sort(new ProviderComparator()); components.sort(new ProviderComparator());
boolean activeRsa = false;
boolean activeHmac = false;
for (ComponentModel c : components) { for (ComponentModel c : components) {
try { try {
ProviderFactory<KeyProvider> f = session.getKeycloakSessionFactory().getProviderFactory(KeyProvider.class, c.getProviderId()); ProviderFactory<KeyProvider> f = session.getKeycloakSessionFactory().getProviderFactory(KeyProvider.class, c.getProviderId());
@ -133,18 +207,30 @@ public class DefaultKeyManager implements KeyManager {
KeyProvider provider = factory.create(session, c); KeyProvider provider = factory.create(session, c);
session.enlistForClose(provider); session.enlistForClose(provider);
providers.add(provider); providers.add(provider);
if (!active && provider.getKid() != null && provider.getPrivateKey() != null) { if (provider.getType().equals(AlgorithmType.RSA)) {
active = true; RsaKeyProvider r = (RsaKeyProvider) provider;
if (r.getKid() != null && r.getPrivateKey() != null) {
activeRsa = true;
}
} else if (provider.getType().equals(AlgorithmType.HMAC)) {
HmacKeyProvider r = (HmacKeyProvider) provider;
if (r.getKid() != null && r.getSecretKey() != null) {
activeHmac = true;
}
} }
} catch (Throwable t) { } catch (Throwable t) {
logger.errorv(t, "Failed to load provider {0}", c.getId()); logger.errorv(t, "Failed to load provider {0}", c.getId());
} }
} }
if (!active) { if (!activeRsa) {
providers.add(new FailsafeRsaKeyProvider()); providers.add(new FailsafeRsaKeyProvider());
} }
if (!activeHmac) {
providers.add(new FailsafeHmacKeyProvider());
}
providersMap.put(realm.getId(), providers); providersMap.put(realm.getId(), providers);
} }
return providers; return providers;

View file

@ -0,0 +1,89 @@
/*
* 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 org.keycloak.models.utils.KeycloakModelUtils;
import javax.crypto.SecretKey;
import java.util.Collections;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class FailsafeHmacKeyProvider implements HmacKeyProvider {
private static final Logger logger = Logger.getLogger(FailsafeHmacKeyProvider.class);
private static String KID;
private static SecretKey KEY;
private static long EXPIRES;
private SecretKey key;
private String kid;
public FailsafeHmacKeyProvider() {
logger.errorv("No active keys found, using failsafe provider, please login to admin console to add keys. Clustering is not supported.");
synchronized (FailsafeHmacKeyProvider.class) {
if (EXPIRES < Time.currentTime()) {
KEY = KeyUtils.loadSecretKey(KeycloakModelUtils.generateSecret(32));
KID = KeycloakModelUtils.generateId();
EXPIRES = Time.currentTime() + 60 * 10;
if (EXPIRES > 0) {
logger.warnv("Keys expired, re-generated kid={0}", KID);
}
}
kid = KID;
key = KEY;
}
}
@Override
public String getKid() {
return kid;
}
@Override
public SecretKey getSecretKey() {
return key;
}
@Override
public SecretKey getSecretKey(String kid) {
return kid.equals(this.kid) ? key : null;
}
@Override
public List<HmacKeyMetadata> getKeyMetadata() {
return Collections.emptyList();
}
@Override
public void close() {
}
}

View file

@ -31,7 +31,7 @@ import java.util.List;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public class FailsafeRsaKeyProvider implements KeyProvider { public class FailsafeRsaKeyProvider implements RsaKeyProvider {
private static final Logger logger = Logger.getLogger(FailsafeRsaKeyProvider.class); private static final Logger logger = Logger.getLogger(FailsafeRsaKeyProvider.class);
@ -85,7 +85,7 @@ public class FailsafeRsaKeyProvider implements KeyProvider {
} }
@Override @Override
public List<KeyMetadata> getKeyMetadata() { public List<RsaKeyMetadata> getKeyMetadata() {
return Collections.emptyList(); return Collections.emptyList();
} }

View file

@ -0,0 +1,102 @@
/*
* 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.component.ComponentModel;
import org.keycloak.jose.jws.AlgorithmType;
import javax.crypto.SecretKey;
import java.util.Collections;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class GeneratedHmacKeyProvider implements HmacKeyProvider {
private final boolean enabled;
private final boolean active;
private final ComponentModel model;
private final String kid;
private final SecretKey secretKey;
public GeneratedHmacKeyProvider(ComponentModel model) {
this.enabled = model.get(Attributes.ENABLED_KEY, true);
this.active = model.get(Attributes.ACTIVE_KEY, true);
this.kid = model.get(Attributes.KID_KEY);
this.model = model;
if (model.hasNote(SecretKey.class.getName())) {
secretKey = model.getNote(SecretKey.class.getName());
} else {
secretKey = KeyUtils.loadSecretKey(model.get(Attributes.SECRET_KEY));
model.setNote(SecretKey.class.getName(), secretKey);
}
}
@Override
public SecretKey getSecretKey() {
return isActive() ? secretKey : null;
}
@Override
public SecretKey getSecretKey(String kid) {
return isEnabled() && kid.equals(this.kid) ? secretKey : null;
}
@Override
public String getKid() {
return isActive() ? kid : null;
}
@Override
public List<HmacKeyMetadata> getKeyMetadata() {
if (kid != null && secretKey != null) {
HmacKeyMetadata k = new HmacKeyMetadata();
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);
}
return Collections.singletonList(k);
} else {
return Collections.emptyList();
}
}
@Override
public void close() {
}
private boolean isEnabled() {
return secretKey != null && enabled;
}
private boolean isActive() {
return isEnabled() && active;
}
}

View file

@ -0,0 +1,111 @@
/*
* 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.Base64Url;
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.models.utils.KeycloakModelUtils;
import org.keycloak.provider.ConfigurationValidationHelper;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class GeneratedHmacKeyProviderFactory extends AbstractHmacKeyProviderFactory {
private static final Logger logger = Logger.getLogger(GeneratedHmacKeyProviderFactory.class);
public static final String ID = "hmac-generated";
private static final String HELP_TEXT = "Generates HMAC secret key";
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = AbstractHmacKeyProviderFactory.configurationBuilder()
.property(Attributes.SECRET_SIZE_PROPERTY)
.build();
@Override
public KeyProvider create(KeycloakSession session, ComponentModel model) {
return new GeneratedHmacKeyProvider(model);
}
@Override
public String getHelpText() {
return HELP_TEXT;
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return CONFIG_PROPERTIES;
}
@Override
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException {
ConfigurationValidationHelper.check(model).checkList(Attributes.SECRET_SIZE_PROPERTY, false);
int size = model.get(Attributes.SECRET_SIZE_KEY, 32);
if (!(model.contains(Attributes.SECRET_KEY))) {
generateSecret(model, size);
logger.debugv("Generated secret for {0}", realm.getName());
} else {
int currentSize = Base64Url.decode(model.get(Attributes.SECRET_KEY)).length;
if (currentSize != size) {
generateSecret(model, size);
logger.debugv("Secret size changed, generating new secret for {0}", realm.getName());
}
}
}
private void generateSecret(ComponentModel model, int size) {
try {
String secret = KeycloakModelUtils.generateSecret(size);
model.put(Attributes.SECRET_KEY, secret);
String kid = KeycloakModelUtils.generateId();
model.put(Attributes.KID_KEY, kid);
} catch (Throwable t) {
throw new ComponentValidationException("Failed to generate secret", t);
}
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return ID;
}
}

View file

@ -53,26 +53,16 @@ public class GeneratedRsaKeyProviderFactory extends AbstractRsaKeyProviderFactor
@Override @Override
public KeyProvider create(KeycloakSession session, ComponentModel model) { public KeyProvider create(KeycloakSession session, ComponentModel model) {
return new RsaKeyProvider(session.getContext().getRealm(), model); return new ImportedRsaKeyProvider(session.getContext().getRealm(), model);
} }
@Override @Override
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException { public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException {
super.validateConfiguration(session, realm, model); super.validateConfiguration(session, realm, model);
ConfigurationValidationHelper.check(model) ConfigurationValidationHelper.check(model).checkList(Attributes.KEY_SIZE_PROPERTY, false);
.checkInt(Attributes.KEY_SIZE_PROPERTY, false);
int size; int size = model.get(Attributes.KEY_SIZE_KEY, 2048);
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))) { if (!(model.contains(Attributes.PRIVATE_KEY_KEY) && model.contains(Attributes.CERTIFICATE_KEY))) {
generateKeys(realm, model, size); generateKeys(realm, model, size);

View file

@ -31,9 +31,9 @@ import java.security.cert.X509Certificate;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public class RsaKeyProvider extends AbstractRsaKeyProvider { public class ImportedRsaKeyProvider extends AbstractRsaKeyProvider {
public RsaKeyProvider(RealmModel realm, ComponentModel model) { public ImportedRsaKeyProvider(RealmModel realm, ComponentModel model) {
super(realm, model); super(realm, model);
} }

View file

@ -33,13 +33,12 @@ import java.security.KeyPair;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.List; import java.util.List;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public class RsaKeyProviderFactory extends AbstractRsaKeyProviderFactory { public class ImportedRsaKeyProviderFactory extends AbstractRsaKeyProviderFactory {
public static final String ID = "rsa"; public static final String ID = "rsa";
@ -52,7 +51,7 @@ public class RsaKeyProviderFactory extends AbstractRsaKeyProviderFactory {
@Override @Override
public KeyProvider create(KeycloakSession session, ComponentModel model) { public KeyProvider create(KeycloakSession session, ComponentModel model) {
return new RsaKeyProvider(session.getContext().getRealm(), model); return new ImportedRsaKeyProvider(session.getContext().getRealm(), model);
} }
@Override @Override

View file

@ -22,6 +22,7 @@ import org.jboss.logging.Logger;
import org.keycloak.common.ClientConnection; import org.keycloak.common.ClientConnection;
import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.HMACProvider;
import org.keycloak.jose.jws.crypto.RSAProvider; import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
@ -31,6 +32,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.util.CookieHelper; import org.keycloak.services.util.CookieHelper;
import javax.crypto.SecretKey;
import javax.ws.rs.core.Cookie; import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import java.security.PublicKey; import java.security.PublicKey;
@ -114,12 +116,11 @@ public class RestartLoginCookie {
} }
public String encode(KeycloakSession session, RealmModel realm) { public String encode(KeycloakSession session, RealmModel realm) {
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm); KeyManager.ActiveHmacKey activeKey = session.keys().getActiveHmacKey(realm);
JWSBuilder builder = new JWSBuilder(); JWSBuilder builder = new JWSBuilder();
return builder.kid(keys.getKid()).jsonContent(this) return builder.kid(activeKey.getKid()).jsonContent(this)
.rsa256(keys.getPrivateKey()); .hmac256(activeKey.getSecretKey());
} }
public RestartLoginCookie() { public RestartLoginCookie() {
@ -157,8 +158,8 @@ public class RestartLoginCookie {
} }
String encodedCookie = cook.getValue(); String encodedCookie = cook.getValue();
JWSInput input = new JWSInput(encodedCookie); JWSInput input = new JWSInput(encodedCookie);
PublicKey publicKey = session.keys().getPublicKey(realm, input.getHeader().getKeyId()); SecretKey secretKey = session.keys().getHmacSecretKey(realm, input.getHeader().getKeyId());
if (!RSAProvider.verify(input, publicKey)) { if (!HMACProvider.verify(input, secretKey)) {
logger.debug("Failed to verify encoded RestartLoginCookie"); logger.debug("Failed to verify encoded RestartLoginCookie");
return null; return null;
} }

View file

@ -56,7 +56,7 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi
RSATokenVerifier verifier = RSATokenVerifier.create(token) RSATokenVerifier verifier = RSATokenVerifier.create(token)
.realmUrl(Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName())); .realmUrl(Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName()));
PublicKey publicKey = session.keys().getPublicKey(realm, verifier.getHeader().getKeyId()); PublicKey publicKey = session.keys().getRsaPublicKey(realm, verifier.getHeader().getKeyId());
if (publicKey == null) { if (publicKey == null) {
valid = false; valid = false;
} else { } else {
@ -96,7 +96,7 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi
RSATokenVerifier verifier = RSATokenVerifier.create(token) RSATokenVerifier verifier = RSATokenVerifier.create(token)
.realmUrl(Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName())); .realmUrl(Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName()));
PublicKey publicKey = session.keys().getPublicKey(realm, verifier.getHeader().getKeyId()); PublicKey publicKey = session.keys().getRsaPublicKey(realm, verifier.getHeader().getKeyId());
verifier.publicKey(publicKey); verifier.publicKey(publicKey);
return verifier.verify().getToken(); return verifier.verify().getToken();

View file

@ -26,6 +26,7 @@ import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.jose.jwk.JWK; import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jwk.JWKBuilder; import org.keycloak.jose.jwk.JWKBuilder;
import org.keycloak.keys.KeyMetadata; import org.keycloak.keys.KeyMetadata;
import org.keycloak.keys.RsaKeyMetadata;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpoint; import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpoint;
@ -187,11 +188,11 @@ public class OIDCLoginProtocolService {
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@NoCache @NoCache
public Response certs() { public Response certs() {
List<KeyMetadata> publicKeys = session.keys().getKeys(realm, false); List<RsaKeyMetadata> publicKeys = session.keys().getRsaKeys(realm, false);
JWK[] keys = new JWK[publicKeys.size()]; JWK[] keys = new JWK[publicKeys.size()];
int i = 0; int i = 0;
for (KeyMetadata k : publicKeys) { for (RsaKeyMetadata k : publicKeys) {
keys[i++] = JWKBuilder.create().kid(k.getKid()).rs256(k.getPublicKey()); keys[i++] = JWKBuilder.create().kid(k.getKid()).rs256(k.getPublicKey());
} }

View file

@ -287,7 +287,7 @@ public class TokenManager {
public RefreshToken toRefreshToken(KeycloakSession session, RealmModel realm, String encodedRefreshToken) throws JWSInputException, OAuthErrorException { public RefreshToken toRefreshToken(KeycloakSession session, RealmModel realm, String encodedRefreshToken) throws JWSInputException, OAuthErrorException {
JWSInput jws = new JWSInput(encodedRefreshToken); JWSInput jws = new JWSInput(encodedRefreshToken);
if (!RSAProvider.verify(jws, session.keys().getPublicKey(realm, jws.getHeader().getKeyId()))) { if (!RSAProvider.verify(jws, session.keys().getRsaPublicKey(realm, jws.getHeader().getKeyId()))) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token"); throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token");
} }
@ -298,7 +298,7 @@ public class TokenManager {
try { try {
JWSInput jws = new JWSInput(encodedIDToken); JWSInput jws = new JWSInput(encodedIDToken);
IDToken idToken; IDToken idToken;
if (!RSAProvider.verify(jws, session.keys().getPublicKey(realm, jws.getHeader().getKeyId()))) { if (!RSAProvider.verify(jws, session.keys().getRsaPublicKey(realm, jws.getHeader().getKeyId()))) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid IDToken"); throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid IDToken");
} }
idToken = jws.readJsonContent(IDToken.class); idToken = jws.readJsonContent(IDToken.class);
@ -626,8 +626,8 @@ public class TokenManager {
} }
public String encodeToken(KeycloakSession session, RealmModel realm, Object token) { public String encodeToken(KeycloakSession session, RealmModel realm, Object token) {
KeyManager.ActiveKey activeKey = session.keys().getActiveKey(realm); KeyManager.ActiveRsaKey activeRsaKey = session.keys().getActiveRsaKey(realm);
return new JWSBuilder().type(JWT).kid(activeKey.getKid()).jsonContent(token).sign(jwsAlgorithm, activeKey.getPrivateKey()); return new JWSBuilder().type(JWT).kid(activeRsaKey.getKid()).jsonContent(token).sign(jwsAlgorithm, activeRsaKey.getPrivateKey());
} }
public AccessTokenResponseBuilder responseBuilder(RealmModel realm, ClientModel client, EventBuilder event, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) { public AccessTokenResponseBuilder responseBuilder(RealmModel realm, ClientModel client, EventBuilder event, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
@ -734,7 +734,7 @@ public class TokenManager {
public AccessTokenResponse build() { public AccessTokenResponse build() {
KeyManager.ActiveKey activeKey = session.keys().getActiveKey(realm); KeyManager.ActiveRsaKey activeRsaKey = session.keys().getActiveRsaKey(realm);
if (accessToken != null) { if (accessToken != null) {
event.detail(Details.TOKEN_ID, accessToken.getId()); event.detail(Details.TOKEN_ID, accessToken.getId());
@ -751,7 +751,7 @@ public class TokenManager {
AccessTokenResponse res = new AccessTokenResponse(); AccessTokenResponse res = new AccessTokenResponse();
if (accessToken != null) { if (accessToken != null) {
String encodedToken = new JWSBuilder().type(JWT).kid(activeKey.getKid()).jsonContent(accessToken).sign(jwsAlgorithm, activeKey.getPrivateKey()); String encodedToken = new JWSBuilder().type(JWT).kid(activeRsaKey.getKid()).jsonContent(accessToken).sign(jwsAlgorithm, activeRsaKey.getPrivateKey());
res.setToken(encodedToken); res.setToken(encodedToken);
res.setTokenType("bearer"); res.setTokenType("bearer");
res.setSessionState(accessToken.getSessionState()); res.setSessionState(accessToken.getSessionState());
@ -769,11 +769,11 @@ public class TokenManager {
} }
if (idToken != null) { if (idToken != null) {
String encodedToken = new JWSBuilder().type(JWT).kid(activeKey.getKid()).jsonContent(idToken).sign(jwsAlgorithm, activeKey.getPrivateKey()); String encodedToken = new JWSBuilder().type(JWT).kid(activeRsaKey.getKid()).jsonContent(idToken).sign(jwsAlgorithm, activeRsaKey.getPrivateKey());
res.setIdToken(encodedToken); res.setIdToken(encodedToken);
} }
if (refreshToken != null) { if (refreshToken != null) {
String encodedToken = new JWSBuilder().type(JWT).kid(activeKey.getKid()).jsonContent(refreshToken).sign(jwsAlgorithm, activeKey.getPrivateKey()); String encodedToken = new JWSBuilder().type(JWT).kid(activeRsaKey.getKid()).jsonContent(refreshToken).sign(jwsAlgorithm, activeRsaKey.getPrivateKey());
res.setRefreshToken(encodedToken); res.setRefreshToken(encodedToken);
if (refreshToken.getExpiration() != 0) { if (refreshToken.getExpiration() != 0) {
res.setRefreshExpiresIn(refreshToken.getExpiration() - Time.currentTime()); res.setRefreshExpiresIn(refreshToken.getExpiration() - Time.currentTime());

View file

@ -132,7 +132,7 @@ public class UserInfoEndpoint {
RSATokenVerifier verifier = RSATokenVerifier.create(tokenString) RSATokenVerifier verifier = RSATokenVerifier.create(tokenString)
.realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())); .realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
String kid = verifier.getHeader().getKeyId(); String kid = verifier.getHeader().getKeyId();
verifier.publicKey(session.keys().getPublicKey(realm, kid)); verifier.publicKey(session.keys().getRsaPublicKey(realm, kid));
token = verifier.verify().getToken(); token = verifier.verify().getToken();
} catch (VerificationException e) { } catch (VerificationException e) {
event.error(Errors.INVALID_TOKEN); event.error(Errors.INVALID_TOKEN);
@ -194,7 +194,7 @@ public class UserInfoEndpoint {
claims.put("aud", audience); claims.put("aud", audience);
Algorithm signatureAlg = cfg.getUserInfoSignedResponseAlg(); Algorithm signatureAlg = cfg.getUserInfoSignedResponseAlg();
PrivateKey privateKey = session.keys().getActiveKey(realm).getPrivateKey(); PrivateKey privateKey = session.keys().getActiveRsaKey(realm).getPrivateKey();
String signedUserInfo = new JWSBuilder() String signedUserInfo = new JWSBuilder()
.jsonContent(claims) .jsonContent(claims)

View file

@ -382,7 +382,7 @@ public class SamlProtocol implements LoginProtocol {
Document samlDocument = null; Document samlDocument = null;
KeyManager keyManager = session.keys(); KeyManager keyManager = session.keys();
KeyManager.ActiveKey keys = keyManager.getActiveKey(realm); KeyManager.ActiveRsaKey keys = keyManager.getActiveRsaKey(realm);
boolean postBinding = isPostBinding(clientSession); boolean postBinding = isPostBinding(clientSession);
String keyName = samlClient.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate()); String keyName = samlClient.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate());
@ -518,7 +518,7 @@ public class SamlProtocol implements LoginProtocol {
String bindingUri = getLogoutServiceUrl(uriInfo, client, SAML_REDIRECT_BINDING); String bindingUri = getLogoutServiceUrl(uriInfo, client, SAML_REDIRECT_BINDING);
SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(bindingUri, clientSession, client); SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(bindingUri, clientSession, client);
if (samlClient.requiresRealmSignature() && samlClient.addExtensionsElementWithKeyInfo()) { if (samlClient.requiresRealmSignature() && samlClient.addExtensionsElementWithKeyInfo()) {
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm); KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
String keyName = samlClient.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate()); String keyName = samlClient.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate());
logoutBuilder.addExtension(new KeycloakKeySamlExtensionGenerator(keyName)); logoutBuilder.addExtension(new KeycloakKeySamlExtensionGenerator(keyName));
} }
@ -561,7 +561,7 @@ public class SamlProtocol implements LoginProtocol {
if (canonicalization != null) { if (canonicalization != null) {
binding.canonicalizationMethod(canonicalization); binding.canonicalizationMethod(canonicalization);
} }
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm); KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
XmlKeyInfoKeyNameTransformer transformer = XmlKeyInfoKeyNameTransformer.from( XmlKeyInfoKeyNameTransformer transformer = XmlKeyInfoKeyNameTransformer.from(
userSession.getNote(SAML_SERVER_SIGNATURE_KEYINFO_KEY_NAME_TRANSFORMER), userSession.getNote(SAML_SERVER_SIGNATURE_KEYINFO_KEY_NAME_TRANSFORMER),
SamlClient.DEFAULT_XML_KEY_INFO_KEY_NAME_TRANSFORMER); SamlClient.DEFAULT_XML_KEY_INFO_KEY_NAME_TRANSFORMER);
@ -668,7 +668,7 @@ public class SamlProtocol implements LoginProtocol {
private JaxrsSAML2BindingBuilder createBindingBuilder(SamlClient samlClient) { private JaxrsSAML2BindingBuilder createBindingBuilder(SamlClient samlClient) {
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder(); JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder();
if (samlClient.requiresRealmSignature()) { if (samlClient.requiresRealmSignature()) {
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm); KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
String keyName = samlClient.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate()); String keyName = samlClient.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate());
binding.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keyName, keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument(); binding.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keyName, keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
} }

View file

@ -36,6 +36,7 @@ import org.keycloak.events.Details;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.keys.RsaKeyMetadata;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeyManager; import org.keycloak.models.KeyManager;
@ -416,7 +417,7 @@ public class SamlService extends AuthorizationEndpointBase {
boolean postBinding = SamlProtocol.SAML_POST_BINDING.equals(logoutBinding); boolean postBinding = SamlProtocol.SAML_POST_BINDING.equals(logoutBinding);
if (samlClient.requiresRealmSignature()) { if (samlClient.requiresRealmSignature()) {
SignatureAlgorithm algorithm = samlClient.getSignatureAlgorithm(); SignatureAlgorithm algorithm = samlClient.getSignatureAlgorithm();
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm); KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
binding.signatureAlgorithm(algorithm).signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument(); binding.signatureAlgorithm(algorithm).signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
if (! postBinding && samlClient.addExtensionsElementWithKeyInfo()) { // Only include extension if REDIRECT binding and signing whole SAML protocol message if (! postBinding && samlClient.addExtensionsElementWithKeyInfo()) { // Only include extension if REDIRECT binding and signing whole SAML protocol message
builder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid())); builder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid()));
@ -567,18 +568,18 @@ public class SamlService extends AuthorizationEndpointBase {
props.put("idp.sso.HTTP-Redirect", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString()); props.put("idp.sso.HTTP-Redirect", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
props.put("idp.sls.HTTP-POST", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString()); props.put("idp.sls.HTTP-POST", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
StringBuilder keysString = new StringBuilder(); StringBuilder keysString = new StringBuilder();
Set<KeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list Set<RsaKeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list
? (int) (o2.getProviderPriority() - o1.getProviderPriority()) ? (int) (o2.getProviderPriority() - o1.getProviderPriority())
: (o1.getStatus() == KeyMetadata.Status.PASSIVE ? 1 : -1)); : (o1.getStatus() == KeyMetadata.Status.PASSIVE ? 1 : -1));
keys.addAll(session.keys().getKeys(realm, false)); keys.addAll(session.keys().getRsaKeys(realm, false));
for (KeyMetadata key : keys) { for (RsaKeyMetadata key : keys) {
addKeyInfo(keysString, key, KeyTypes.SIGNING.value()); addKeyInfo(keysString, key, KeyTypes.SIGNING.value());
} }
props.put("idp.signing.certificates", keysString.toString()); props.put("idp.signing.certificates", keysString.toString());
return StringPropertyReplacer.replaceProperties(template, props); return StringPropertyReplacer.replaceProperties(template, props);
} }
private static void addKeyInfo(StringBuilder target, KeyMetadata key, String purpose) { private static void addKeyInfo(StringBuilder target, RsaKeyMetadata key, String purpose) {
if (key == null) { if (key == null) {
return; return;
} }

View file

@ -19,6 +19,7 @@ package org.keycloak.protocol.saml.installation;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.common.util.PemUtils; import org.keycloak.common.util.PemUtils;
import org.keycloak.keys.RsaKeyMetadata;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
@ -87,11 +88,11 @@ public class SamlIDPDescriptorClientInstallation implements ClientInstallationPr
} }
// keys // keys
Set<KeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list Set<RsaKeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list
? (int) (o2.getProviderPriority() - o1.getProviderPriority()) ? (int) (o2.getProviderPriority() - o1.getProviderPriority())
: (o1.getStatus() == KeyMetadata.Status.PASSIVE ? 1 : -1)); : (o1.getStatus() == KeyMetadata.Status.PASSIVE ? 1 : -1));
keys.addAll(session.keys().getKeys(realm, false)); keys.addAll(session.keys().getRsaKeys(realm, false));
for (KeyMetadata key : keys) { for (RsaKeyMetadata key : keys) {
addKeyInfo(sb, key, KeyTypes.SIGNING.value()); addKeyInfo(sb, key, KeyTypes.SIGNING.value());
} }
@ -100,7 +101,7 @@ public class SamlIDPDescriptorClientInstallation implements ClientInstallationPr
return sb.toString(); return sb.toString();
} }
private static void addKeyInfo(StringBuilder target, KeyMetadata key, String purpose) { private static void addKeyInfo(StringBuilder target, RsaKeyMetadata key, String purpose) {
if (key == null) { if (key == null) {
return; return;
} }

View file

@ -75,7 +75,7 @@ public class ClientRegistrationTokenUtils {
return TokenVerification.error(new RuntimeException("Invalid token", e)); return TokenVerification.error(new RuntimeException("Invalid token", e));
} }
PublicKey publicKey = session.keys().getPublicKey(realm, input.getHeader().getKeyId()); PublicKey publicKey = session.keys().getRsaPublicKey(realm, input.getHeader().getKeyId());
if (!RSAProvider.verify(input, publicKey)) { if (!RSAProvider.verify(input, publicKey)) {
return TokenVerification.error(new RuntimeException("Failed verify token")); return TokenVerification.error(new RuntimeException("Failed verify token"));
@ -115,7 +115,7 @@ public class ClientRegistrationTokenUtils {
jwt.issuer(issuer); jwt.issuer(issuer);
jwt.audience(issuer); jwt.audience(issuer);
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm); KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
String token = new JWSBuilder().kid(keys.getKid()).jsonContent(jwt).rsa256(keys.getPrivateKey()); String token = new JWSBuilder().kid(keys.getKid()).jsonContent(jwt).rsa256(keys.getPrivateKey());
return token; return token;

View file

@ -19,7 +19,7 @@ package org.keycloak.services.managers;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl; import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.RSATokenVerifier; import org.keycloak.TokenVerifier;
import org.keycloak.authentication.RequiredActionContext; import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionContextResult; import org.keycloak.authentication.RequiredActionContextResult;
import org.keycloak.authentication.RequiredActionFactory; import org.keycloak.authentication.RequiredActionFactory;
@ -33,6 +33,7 @@ import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.forms.login.LoginFormsProvider; import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.jose.jws.AlgorithmType;
import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
@ -58,6 +59,7 @@ import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.util.CookieHelper; import org.keycloak.services.util.CookieHelper;
import org.keycloak.services.util.P3PHelper; import org.keycloak.services.util.P3PHelper;
import javax.crypto.SecretKey;
import javax.ws.rs.core.Cookie; import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
@ -113,12 +115,12 @@ public class AuthenticationManager {
if (cookie == null) return; if (cookie == null) return;
String tokenString = cookie.getValue(); String tokenString = cookie.getValue();
RSATokenVerifier verifier = RSATokenVerifier.create(tokenString).realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())).checkActive(false).checkTokenType(false); TokenVerifier verifier = TokenVerifier.create(tokenString).realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())).checkActive(false).checkTokenType(false);
String kid = verifier.getHeader().getKeyId(); String kid = verifier.getHeader().getKeyId();
PublicKey publicKey = session.keys().getPublicKey(realm, kid); SecretKey secretKey = session.keys().getHmacSecretKey(realm, kid);
AccessToken token = verifier.publicKey(publicKey).verify().getToken(); AccessToken token = verifier.secretKey(secretKey).verify().getToken();
UserSessionModel cookieSession = session.sessions().getUserSession(realm, token.getSessionState()); UserSessionModel cookieSession = session.sessions().getUserSession(realm, token.getSessionState());
if (cookieSession == null || !cookieSession.getId().equals(userSession.getId())) return; if (cookieSession == null || !cookieSession.getId().equals(userSession.getId())) return;
expireIdentityCookie(realm, uriInfo, connection); expireIdentityCookie(realm, uriInfo, connection);
@ -337,14 +339,14 @@ public class AuthenticationManager {
} }
protected static String encodeToken(KeycloakSession session, RealmModel realm, Object token) { protected static String encodeToken(KeycloakSession session, RealmModel realm, Object token) {
KeyManager.ActiveKey activeKey = session.keys().getActiveKey(realm); KeyManager.ActiveHmacKey activeKey = session.keys().getActiveHmacKey(realm);
logger.tracef("Encoding token with kid '%s'", activeKey.getKid()); logger.tracef("Encoding token with kid '%s'", activeKey.getKid());
String encodedToken = new JWSBuilder() String encodedToken = new JWSBuilder()
.kid(activeKey.getKid()) .kid(activeKey.getKid())
.jsonContent(token) .jsonContent(token)
.rsa256(activeKey.getPrivateKey()); .hmac256(activeKey.getSecretKey());
return encodedToken; return encodedToken;
} }
@ -691,15 +693,25 @@ public class AuthenticationManager {
protected static AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, boolean checkTokenType, protected static AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, boolean checkTokenType,
String tokenString, HttpHeaders headers) { String tokenString, HttpHeaders headers) {
try { try {
RSATokenVerifier verifier = RSATokenVerifier.create(tokenString).realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())).checkActive(checkActive).checkTokenType(checkTokenType); TokenVerifier verifier = TokenVerifier.create(tokenString).realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())).checkActive(checkActive).checkTokenType(checkTokenType);
String kid = verifier.getHeader().getKeyId(); String kid = verifier.getHeader().getKeyId();
AlgorithmType algorithmType = verifier.getHeader().getAlgorithm().getType();
PublicKey publicKey = session.keys().getPublicKey(realm, kid); if (AlgorithmType.RSA.equals(algorithmType)) {
PublicKey publicKey = session.keys().getRsaPublicKey(realm, kid);
if (publicKey == null) { if (publicKey == null) {
logger.debugf("Identity cookie signed with unknown kid '%s'", kid); logger.debugf("Identity cookie signed with unknown kid '%s'", kid);
return null; return null;
} }
verifier.publicKey(publicKey); verifier.publicKey(publicKey);
} else if (AlgorithmType.HMAC.equals(algorithmType)) {
SecretKey secretKey = session.keys().getHmacSecretKey(realm, kid);
if (secretKey == null) {
logger.debugf("Identity cookie signed with unknown kid '%s'", kid);
return null;
}
verifier.secretKey(secretKey);
}
AccessToken token = verifier.verify().getToken(); AccessToken token = verifier.verify().getToken();
if (checkActive) { if (checkActive) {

View file

@ -93,7 +93,7 @@ public class PublicRealmResource {
rep.setTokenServiceUrl(OIDCLoginProtocolService.tokenServiceBaseUrl(uriInfo).build(realm.getName()).toString()); rep.setTokenServiceUrl(OIDCLoginProtocolService.tokenServiceBaseUrl(uriInfo).build(realm.getName()).toString());
rep.setAccountServiceUrl(AccountService.accountServiceBaseUrl(uriInfo).build(realm.getName()).toString()); rep.setAccountServiceUrl(AccountService.accountServiceBaseUrl(uriInfo).build(realm.getName()).toString());
rep.setAdminApiUrl(uriInfo.getBaseUriBuilder().path(AdminRoot.class).build().toString()); rep.setAdminApiUrl(uriInfo.getBaseUriBuilder().path(AdminRoot.class).build().toString());
rep.setPublicKeyPem(PemUtils.encodeKey(session.keys().getActiveKey(realm).getPublicKey())); rep.setPublicKeyPem(PemUtils.encodeKey(session.keys().getActiveRsaKey(realm).getPublicKey()));
rep.setNotBefore(realm.getNotBefore()); rep.setNotBefore(realm.getNotBefore());
return rep; return rep;
} }

View file

@ -34,7 +34,6 @@ import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.utils.JWKSHttpUtils;
import org.keycloak.representations.KeyStoreConfig; import org.keycloak.representations.KeyStoreConfig;
import org.keycloak.representations.idm.CertificateRepresentation; import org.keycloak.representations.idm.CertificateRepresentation;
import org.keycloak.services.ErrorResponseException; import org.keycloak.services.ErrorResponseException;
@ -374,8 +373,8 @@ public class ClientAttributeCertificateResource {
if (config.isRealmCertificate() == null || config.isRealmCertificate().booleanValue()) { if (config.isRealmCertificate() == null || config.isRealmCertificate().booleanValue()) {
KeyManager keys = session.keys(); KeyManager keys = session.keys();
String kid = keys.getActiveKey(realm).getKid(); String kid = keys.getActiveRsaKey(realm).getKid();
Certificate certificate = keys.getCertificate(realm, kid); Certificate certificate = keys.getRsaCertificate(realm, kid);
String certificateAlias = config.getRealmAlias(); String certificateAlias = config.getRealmAlias();
if (certificateAlias == null) certificateAlias = realm.getName(); if (certificateAlias == null) certificateAlias = realm.getName();
keyStore.setCertificateEntry(certificateAlias, certificate); keyStore.setCertificateEntry(certificateAlias, certificate);

View file

@ -19,7 +19,9 @@ package org.keycloak.services.resources.admin;
import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.common.util.PemUtils; import org.keycloak.common.util.PemUtils;
import org.keycloak.keys.KeyMetadata; import org.keycloak.jose.jws.AlgorithmType;
import org.keycloak.keys.HmacKeyMetadata;
import org.keycloak.keys.RsaKeyMetadata;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeyManager; import org.keycloak.models.KeyManager;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
@ -28,9 +30,10 @@ import org.keycloak.representations.idm.KeysMetadataRepresentation;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import java.util.Collections; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -56,20 +59,33 @@ public class KeyResource {
KeyManager keystore = session.keys(); KeyManager keystore = session.keys();
KeysMetadataRepresentation keys = new KeysMetadataRepresentation(); KeysMetadataRepresentation keys = new KeysMetadataRepresentation();
keys.setActive(Collections.singletonMap(KeyMetadata.Type.RSA.name(), keystore.getActiveKey(realm).getKid()));
Map<String, String> active = new HashMap<>();
active.put(AlgorithmType.RSA.name(), keystore.getActiveRsaKey(realm).getKid());
active.put(AlgorithmType.HMAC.name(), keystore.getActiveHmacKey(realm).getKid());
keys.setActive(active);
List<KeysMetadataRepresentation.KeyMetadataRepresentation> l = new LinkedList<>(); List<KeysMetadataRepresentation.KeyMetadataRepresentation> l = new LinkedList<>();
for (KeyMetadata m : session.keys().getKeys(realm, true)) { for (RsaKeyMetadata m : session.keys().getRsaKeys(realm, true)) {
KeysMetadataRepresentation.KeyMetadataRepresentation r = new KeysMetadataRepresentation.KeyMetadataRepresentation(); KeysMetadataRepresentation.KeyMetadataRepresentation r = new KeysMetadataRepresentation.KeyMetadataRepresentation();
r.setProviderId(m.getProviderId()); r.setProviderId(m.getProviderId());
r.setProviderPriority(m.getProviderPriority()); r.setProviderPriority(m.getProviderPriority());
r.setKid(m.getKid()); r.setKid(m.getKid());
r.setStatus(m.getStatus() != null ? m.getStatus().name() : null); r.setStatus(m.getStatus() != null ? m.getStatus().name() : null);
r.setType(m.getType() != null ? m.getType().name() : null); r.setType(AlgorithmType.RSA.name());
r.setPublicKey(PemUtils.encodeKey(m.getPublicKey())); r.setPublicKey(PemUtils.encodeKey(m.getPublicKey()));
r.setCertificate(PemUtils.encodeCertificate(m.getCertificate())); r.setCertificate(PemUtils.encodeCertificate(m.getCertificate()));
l.add(r); l.add(r);
} }
for (HmacKeyMetadata m : session.keys().getHmacKeys(realm, true)) {
KeysMetadataRepresentation.KeyMetadataRepresentation r = new KeysMetadataRepresentation.KeyMetadataRepresentation();
r.setProviderId(m.getProviderId());
r.setProviderPriority(m.getProviderPriority());
r.setKid(m.getKid());
r.setStatus(m.getStatus() != null ? m.getStatus().name() : null);
r.setType(AlgorithmType.HMAC.name());
l.add(r);
}
keys.setKeys(l); keys.setKeys(l);

View file

@ -15,6 +15,7 @@
# limitations under the License. # limitations under the License.
# #
org.keycloak.keys.GeneratedHmacKeyProviderFactory
org.keycloak.keys.GeneratedRsaKeyProviderFactory org.keycloak.keys.GeneratedRsaKeyProviderFactory
org.keycloak.keys.JavaKeystoreKeyProviderFactory org.keycloak.keys.JavaKeystoreKeyProviderFactory
org.keycloak.keys.RsaKeyProviderFactory org.keycloak.keys.ImportedRsaKeyProviderFactory

View file

@ -20,6 +20,7 @@ package org.keycloak.testsuite.rest;
import org.infinispan.Cache; import org.infinispan.Cache;
import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.BadRequestException; import org.jboss.resteasy.spi.BadRequestException;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider; import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
@ -633,6 +634,14 @@ public class TestingResourceProvider implements RealmResourceProvider {
return reps; return reps;
} }
@GET
@Path("/component")
@Produces(MediaType.APPLICATION_JSON)
public MultivaluedHashMap<String, String> getComponentConfig(@QueryParam("componentId") String componentId) {
RealmModel realm = session.getContext().getRealm();
return realm.getComponent(componentId).getConfig();
}
@GET @GET
@Path("/smtp-config") @Path("/smtp-config")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)

View file

@ -18,6 +18,7 @@
package org.keycloak.testsuite.client.resources; package org.keycloak.testsuite.client.resources;
import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.representations.idm.AdminEventRepresentation; import org.keycloak.representations.idm.AdminEventRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.EventRepresentation;
@ -251,4 +252,8 @@ public interface TestingResource {
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
Map<String, String> getIdentityProviderConfig(@QueryParam("alias") String alias); Map<String, String> getIdentityProviderConfig(@QueryParam("alias") String alias);
@GET
@Path("/component")
@Produces(MediaType.APPLICATION_JSON)
MultivaluedHashMap<String, String> getComponentConfig(@QueryParam("componentId") String componentId);
} }

View file

@ -19,6 +19,7 @@ package org.keycloak.testsuite.adapter.servlet;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
@ -41,6 +42,7 @@ import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.StreamUtil; import org.keycloak.common.util.StreamUtil;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.constants.AdapterConstants; import org.keycloak.constants.AdapterConstants;
import org.keycloak.jose.jws.AlgorithmType;
import org.keycloak.keys.KeyProvider; import org.keycloak.keys.KeyProvider;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
@ -48,6 +50,7 @@ import org.keycloak.representations.AccessToken;
import org.keycloak.representations.adapters.action.GlobalRequestResult; import org.keycloak.representations.adapters.action.GlobalRequestResult;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.KeysMetadataRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest; import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
import org.keycloak.testsuite.adapter.filter.AdapterActionsFilter; import org.keycloak.testsuite.adapter.filter.AdapterActionsFilter;
@ -58,9 +61,7 @@ import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.util.URLAssert; import org.keycloak.testsuite.util.URLAssert;
import org.openqa.selenium.By; import org.openqa.selenium.By;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.*;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO; import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals; import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith; import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
@ -201,9 +202,9 @@ public abstract class AbstractOIDCPublicKeyRotationAdapterTest extends AbstractS
Assert.assertEquals(200, status); Assert.assertEquals(200, status);
// Re-generate realm public key and remove the old key // Re-generate realm public key and remove the old key
String oldKeyId = getActiveKeyId(); String oldActiveKeyProviderId = getActiveKeyProvider();
generateNewRealmKey(); generateNewRealmKey();
adminClient.realm(DEMO).components().component(oldKeyId).remove(); adminClient.realm(DEMO).components().component(oldActiveKeyProviderId).remove();
// Send REST request to the customer-db app. Should be still succcessfully authenticated as the JWKPublicKeyLocator cache is still valid // Send REST request to the customer-db app. Should be still succcessfully authenticated as the JWKPublicKeyLocator cache is still valid
status = invokeRESTEndpoint(accessTokenString); status = invokeRESTEndpoint(accessTokenString);
@ -237,7 +238,8 @@ public abstract class AbstractOIDCPublicKeyRotationAdapterTest extends AbstractS
String accessTokenString = tokenMinTTLPage.getAccessTokenString(); String accessTokenString = tokenMinTTLPage.getAccessTokenString();
// Generate new realm public key // Generate new realm public key
String oldKeyId = getActiveKeyId(); String oldActiveKeyProviderId = getActiveKeyProvider();
generateNewRealmKey(); generateNewRealmKey();
// Send REST request to customer-db app. It should be successfully authenticated even that token is signed by the old key // Send REST request to customer-db app. It should be successfully authenticated even that token is signed by the old key
@ -245,7 +247,7 @@ public abstract class AbstractOIDCPublicKeyRotationAdapterTest extends AbstractS
Assert.assertEquals(200, status); Assert.assertEquals(200, status);
// Remove the old realm key now // Remove the old realm key now
adminClient.realm(DEMO).components().component(oldKeyId).remove(); adminClient.realm(DEMO).components().component(oldActiveKeyProviderId).remove();
// Set some offset to ensure pushing notBefore will pass // Set some offset to ensure pushing notBefore will pass
setAdapterAndServerTimeOffset(130, customerDb.toString() + "/unsecured/foo", tokenMinTTLPage.toString() + "/unsecured/foo"); setAdapterAndServerTimeOffset(130, customerDb.toString() + "/unsecured/foo", tokenMinTTLPage.toString() + "/unsecured/foo");
@ -295,12 +297,16 @@ public abstract class AbstractOIDCPublicKeyRotationAdapterTest extends AbstractS
response.close(); response.close();
} }
private String getActiveKeyId() { private String getActiveKeyProvider() {
String realmId = adminClient.realm(DEMO).toRepresentation().getId(); KeysMetadataRepresentation keyMetadata = adminClient.realm(DEMO).keys().getKeyMetadata();
return adminClient.realm(DEMO).components().query(realmId, KeyProvider.class.getName()) String activeKid = keyMetadata.getActive().get(AlgorithmType.RSA.name());
.get(0).getId(); for (KeysMetadataRepresentation.KeyMetadataRepresentation rep : keyMetadata.getKeys()) {
if (rep.getKid().equals(activeKid)) {
return rep.getProviderId();
}
}
return null;
} }
private int invokeRESTEndpoint(String accessTokenString) { private int invokeRESTEndpoint(String accessTokenString) {

View file

@ -30,8 +30,7 @@ import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.PemUtils; import org.keycloak.common.util.PemUtils;
import org.keycloak.keys.Attributes; import org.keycloak.keys.Attributes;
import org.keycloak.keys.KeyProvider; import org.keycloak.keys.KeyProvider;
import org.keycloak.keys.RsaKeyProviderFactory; import org.keycloak.keys.ImportedRsaKeyProviderFactory;
import org.keycloak.protocol.saml.SamlClient;
import org.keycloak.protocol.saml.SamlConfigAttributes; import org.keycloak.protocol.saml.SamlConfigAttributes;
import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.protocol.saml.mappers.AttributeStatementHelper; import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
@ -452,7 +451,7 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
ComponentRepresentation rep = new ComponentRepresentation(); ComponentRepresentation rep = new ComponentRepresentation();
rep.setName("mycomponent"); rep.setName("mycomponent");
rep.setParentId("demo"); rep.setParentId("demo");
rep.setProviderId(RsaKeyProviderFactory.ID); rep.setProviderId(ImportedRsaKeyProviderFactory.ID);
rep.setProviderType(KeyProvider.class.getName()); rep.setProviderType(KeyProvider.class.getName());
org.keycloak.common.util.MultivaluedHashMap config = new org.keycloak.common.util.MultivaluedHashMap(); org.keycloak.common.util.MultivaluedHashMap config = new org.keycloak.common.util.MultivaluedHashMap();

View file

@ -0,0 +1,182 @@
/*
* 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.testsuite.keys;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.PemUtils;
import org.keycloak.jose.jws.AlgorithmType;
import org.keycloak.jose.jws.crypto.HMACProvider;
import org.keycloak.keys.GeneratedHmacKeyProviderFactory;
import org.keycloak.keys.GeneratedRsaKeyProviderFactory;
import org.keycloak.keys.KeyProvider;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.idm.KeysMetadataRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LoginPage;
import javax.ws.rs.core.Response;
import java.security.interfaces.RSAPublicKey;
import java.util.List;
import static org.junit.Assert.*;
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class GeneratedHmacKeyProviderTest extends AbstractKeycloakTest {
@Rule
public AssertEvents events = new AssertEvents(this);
@Page
protected AppPage appPage;
@Page
protected LoginPage loginPage;
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class);
testRealms.add(realm);
}
@Test
public void defaultKeysize() throws Exception {
long priority = System.currentTimeMillis();
ComponentRepresentation rep = createRep("valid", GeneratedHmacKeyProviderFactory.ID);
rep.setConfig(new MultivaluedHashMap<>());
rep.getConfig().putSingle("priority", Long.toString(priority));
Response response = adminClient.realm("test").components().add(rep);
String id = ApiUtil.getCreatedId(response);
ComponentRepresentation createdRep = adminClient.realm("test").components().component(id).toRepresentation();
assertEquals(1, createdRep.getConfig().size());
assertEquals(Long.toString(priority), createdRep.getConfig().getFirst("priority"));
KeysMetadataRepresentation keys = adminClient.realm("test").keys().getKeyMetadata();
KeysMetadataRepresentation.KeyMetadataRepresentation key = null;
for (KeysMetadataRepresentation.KeyMetadataRepresentation k : keys.getKeys()) {
if (k.getType().equals(AlgorithmType.HMAC.name())) {
key = k;
break;
}
}
assertEquals(id, key.getProviderId());
assertEquals(AlgorithmType.HMAC.name(), key.getType());
assertEquals(priority, key.getProviderPriority());
assertEquals(32, Base64Url.decode(testingClient.testing("test").getComponentConfig(id).getFirst("secret")).length);
}
@Test
public void largeKeysize() throws Exception {
long priority = System.currentTimeMillis();
ComponentRepresentation rep = createRep("valid", GeneratedHmacKeyProviderFactory.ID);
rep.setConfig(new MultivaluedHashMap<>());
rep.getConfig().putSingle("priority", Long.toString(priority));
rep.getConfig().putSingle("secretSize", "512");
Response response = adminClient.realm("test").components().add(rep);
String id = ApiUtil.getCreatedId(response);
ComponentRepresentation createdRep = adminClient.realm("test").components().component(id).toRepresentation();
assertEquals(2, createdRep.getConfig().size());
assertEquals("512", createdRep.getConfig().getFirst("secretSize"));
KeysMetadataRepresentation keys = adminClient.realm("test").keys().getKeyMetadata();
KeysMetadataRepresentation.KeyMetadataRepresentation key = null;
for (KeysMetadataRepresentation.KeyMetadataRepresentation k : keys.getKeys()) {
if (k.getType().equals(AlgorithmType.HMAC.name())) {
key = k;
break;
}
}
assertEquals(id, key.getProviderId());
assertEquals(AlgorithmType.HMAC.name(), key.getType());
assertEquals(priority, key.getProviderPriority());
assertEquals(512, Base64Url.decode(testingClient.testing("test").getComponentConfig(id).getFirst("secret")).length);
}
@Test
public void updateKeysize() throws Exception {
long priority = System.currentTimeMillis();
ComponentRepresentation rep = createRep("valid", GeneratedHmacKeyProviderFactory.ID);
rep.setConfig(new MultivaluedHashMap<>());
rep.getConfig().putSingle("priority", Long.toString(priority));
Response response = adminClient.realm("test").components().add(rep);
String id = ApiUtil.getCreatedId(response);
assertEquals(32, Base64Url.decode(testingClient.testing("test").getComponentConfig(id).getFirst("secret")).length);
ComponentRepresentation createdRep = adminClient.realm("test").components().component(id).toRepresentation();
createdRep.getConfig().putSingle("secretSize", "512");
adminClient.realm("test").components().component(id).update(createdRep);
assertEquals(512, Base64Url.decode(testingClient.testing("test").getComponentConfig(id).getFirst("secret")).length);
}
@Test
public void invalidKeysize() throws Exception {
ComponentRepresentation rep = createRep("invalid", GeneratedHmacKeyProviderFactory.ID);
rep.getConfig().putSingle("secretSize", "1234");
Response response = adminClient.realm("test").components().add(rep);
assertErrror(response, "'Secret size' should be 32, 64, 128, 256 or 512");
}
protected void assertErrror(Response response, String error) {
if (!response.hasEntity()) {
fail("No error message set");
}
ErrorRepresentation errorRepresentation = response.readEntity(ErrorRepresentation.class);
assertEquals(error, errorRepresentation.getErrorMessage());
}
protected ComponentRepresentation createRep(String name, String providerId) {
ComponentRepresentation rep = new ComponentRepresentation();
rep.setName(name);
rep.setParentId("test");
rep.setProviderId(providerId);
rep.setProviderType(KeyProvider.class.getName());
rep.setConfig(new MultivaluedHashMap<>());
return rep;
}
}

View file

@ -22,6 +22,7 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.PemUtils; import org.keycloak.common.util.PemUtils;
import org.keycloak.jose.jws.AlgorithmType;
import org.keycloak.keys.GeneratedRsaKeyProviderFactory; import org.keycloak.keys.GeneratedRsaKeyProviderFactory;
import org.keycloak.keys.KeyMetadata; import org.keycloak.keys.KeyMetadata;
import org.keycloak.keys.KeyProvider; import org.keycloak.keys.KeyProvider;
@ -45,7 +46,7 @@ import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public class RsaGeneratedKeyProviderTest extends AbstractKeycloakTest { public class GeneratedRsaKeyProviderTest extends AbstractKeycloakTest {
@Rule @Rule
public AssertEvents events = new AssertEvents(this); public AssertEvents events = new AssertEvents(this);
@ -74,16 +75,15 @@ public class RsaGeneratedKeyProviderTest extends AbstractKeycloakTest {
String id = ApiUtil.getCreatedId(response); String id = ApiUtil.getCreatedId(response);
ComponentRepresentation createdRep = adminClient.realm("test").components().component(id).toRepresentation(); ComponentRepresentation createdRep = adminClient.realm("test").components().component(id).toRepresentation();
assertEquals(2, createdRep.getConfig().size()); assertEquals(1, createdRep.getConfig().size());
assertEquals(Long.toString(priority), createdRep.getConfig().getFirst("priority")); assertEquals(Long.toString(priority), createdRep.getConfig().getFirst("priority"));
assertEquals("2048", createdRep.getConfig().getFirst("keySize"));
KeysMetadataRepresentation keys = adminClient.realm("test").keys().getKeyMetadata(); KeysMetadataRepresentation keys = adminClient.realm("test").keys().getKeyMetadata();
KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0); KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0);
assertEquals(id, key.getProviderId()); assertEquals(id, key.getProviderId());
assertEquals(KeyMetadata.Type.RSA.name(), key.getType()); assertEquals(AlgorithmType.RSA.name(), key.getType());
assertEquals(priority, key.getProviderPriority()); assertEquals(priority, key.getProviderPriority());
assertEquals(2048, ((RSAPublicKey) PemUtils.decodePublicKey(keys.getKeys().get(0).getPublicKey())).getModulus().bitLength()); assertEquals(2048, ((RSAPublicKey) PemUtils.decodePublicKey(keys.getKeys().get(0).getPublicKey())).getModulus().bitLength());
} }
@ -109,7 +109,7 @@ public class RsaGeneratedKeyProviderTest extends AbstractKeycloakTest {
KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0); KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0);
assertEquals(id, key.getProviderId()); assertEquals(id, key.getProviderId());
assertEquals(KeyMetadata.Type.RSA.name(), key.getType()); assertEquals(AlgorithmType.RSA.name(), key.getType());
assertEquals(priority, key.getProviderPriority()); assertEquals(priority, key.getProviderPriority());
assertEquals(4096, ((RSAPublicKey) PemUtils.decodePublicKey(keys.getKeys().get(0).getPublicKey())).getModulus().bitLength()); assertEquals(4096, ((RSAPublicKey) PemUtils.decodePublicKey(keys.getKeys().get(0).getPublicKey())).getModulus().bitLength());
} }
@ -177,7 +177,7 @@ public class RsaGeneratedKeyProviderTest extends AbstractKeycloakTest {
rep.getConfig().putSingle("keySize", "1234"); rep.getConfig().putSingle("keySize", "1234");
Response response = adminClient.realm("test").components().add(rep); Response response = adminClient.realm("test").components().add(rep);
assertErrror(response, "Keysize should be 1024, 2048 or 4096"); assertErrror(response, "'Key size' should be 1024, 2048 or 4096");
} }
protected void assertErrror(Response response, String error) { protected void assertErrror(Response response, String error) {

View file

@ -24,10 +24,11 @@ import org.keycloak.common.util.CertificateUtils;
import org.keycloak.common.util.KeyUtils; import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.PemUtils; import org.keycloak.common.util.PemUtils;
import org.keycloak.jose.jws.AlgorithmType;
import org.keycloak.keys.Attributes; import org.keycloak.keys.Attributes;
import org.keycloak.keys.ImportedRsaKeyProviderFactory;
import org.keycloak.keys.KeyMetadata; import org.keycloak.keys.KeyMetadata;
import org.keycloak.keys.KeyProvider; import org.keycloak.keys.KeyProvider;
import org.keycloak.keys.RsaKeyProviderFactory;
import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.ErrorRepresentation; import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.idm.KeysMetadataRepresentation; import org.keycloak.representations.idm.KeysMetadataRepresentation;
@ -49,7 +50,7 @@ import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public class RsaKeyProviderTest extends AbstractKeycloakTest { public class ImportedRsaKeyProviderTest extends AbstractKeycloakTest {
@Rule @Rule
public AssertEvents events = new AssertEvents(this); public AssertEvents events = new AssertEvents(this);
@ -73,7 +74,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest {
KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048); KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
String kid = KeyUtils.createKeyId(keyPair.getPublic()); String kid = KeyUtils.createKeyId(keyPair.getPublic());
ComponentRepresentation rep = createRep("valid", RsaKeyProviderFactory.ID); ComponentRepresentation rep = createRep("valid", ImportedRsaKeyProviderFactory.ID);
rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate())); rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate()));
rep.getConfig().putSingle(Attributes.PRIORITY_KEY, Long.toString(priority)); rep.getConfig().putSingle(Attributes.PRIORITY_KEY, Long.toString(priority));
@ -88,12 +89,12 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest {
KeysMetadataRepresentation keys = adminClient.realm("test").keys().getKeyMetadata(); KeysMetadataRepresentation keys = adminClient.realm("test").keys().getKeyMetadata();
assertEquals(kid, keys.getActive().get(KeyMetadata.Type.RSA.name())); assertEquals(kid, keys.getActive().get(AlgorithmType.RSA.name()));
KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0); KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0);
assertEquals(id, key.getProviderId()); assertEquals(id, key.getProviderId());
assertEquals(KeyMetadata.Type.RSA.name(), key.getType()); assertEquals(AlgorithmType.RSA.name(), key.getType());
assertEquals(priority, key.getProviderPriority()); assertEquals(priority, key.getProviderPriority());
assertEquals(kid, key.getKid()); assertEquals(kid, key.getKid());
assertEquals(PemUtils.encodeKey(keyPair.getPublic()), keys.getKeys().get(0).getPublicKey()); assertEquals(PemUtils.encodeKey(keyPair.getPublic()), keys.getKeys().get(0).getPublicKey());
@ -108,7 +109,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest {
Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, "test"); Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, "test");
String certificatePem = PemUtils.encodeCertificate(certificate); String certificatePem = PemUtils.encodeCertificate(certificate);
ComponentRepresentation rep = createRep("valid", RsaKeyProviderFactory.ID); ComponentRepresentation rep = createRep("valid", ImportedRsaKeyProviderFactory.ID);
rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate())); rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate()));
rep.getConfig().putSingle(Attributes.CERTIFICATE_KEY, certificatePem); rep.getConfig().putSingle(Attributes.CERTIFICATE_KEY, certificatePem);
rep.getConfig().putSingle(Attributes.PRIORITY_KEY, Long.toString(priority)); rep.getConfig().putSingle(Attributes.PRIORITY_KEY, Long.toString(priority));
@ -130,7 +131,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest {
public void invalidPriority() throws Exception { public void invalidPriority() throws Exception {
KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048); KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
ComponentRepresentation rep = createRep("invalid", RsaKeyProviderFactory.ID); ComponentRepresentation rep = createRep("invalid", ImportedRsaKeyProviderFactory.ID);
rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate())); rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate()));
rep.getConfig().putSingle(Attributes.PRIORITY_KEY, "invalid"); rep.getConfig().putSingle(Attributes.PRIORITY_KEY, "invalid");
@ -142,7 +143,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest {
public void invalidEnabled() throws Exception { public void invalidEnabled() throws Exception {
KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048); KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
ComponentRepresentation rep = createRep("invalid", RsaKeyProviderFactory.ID); ComponentRepresentation rep = createRep("invalid", ImportedRsaKeyProviderFactory.ID);
rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate())); rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate()));
rep.getConfig().putSingle(Attributes.ENABLED_KEY, "invalid"); rep.getConfig().putSingle(Attributes.ENABLED_KEY, "invalid");
@ -154,7 +155,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest {
public void invalidActive() throws Exception { public void invalidActive() throws Exception {
KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048); KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
ComponentRepresentation rep = createRep("invalid", RsaKeyProviderFactory.ID); ComponentRepresentation rep = createRep("invalid", ImportedRsaKeyProviderFactory.ID);
rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate())); rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate()));
rep.getConfig().putSingle(Attributes.ACTIVE_KEY, "invalid"); rep.getConfig().putSingle(Attributes.ACTIVE_KEY, "invalid");
@ -166,7 +167,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest {
public void invalidPrivateKey() throws Exception { public void invalidPrivateKey() throws Exception {
KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048); KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
ComponentRepresentation rep = createRep("invalid", RsaKeyProviderFactory.ID); ComponentRepresentation rep = createRep("invalid", ImportedRsaKeyProviderFactory.ID);
Response response = adminClient.realm("test").components().add(rep); Response response = adminClient.realm("test").components().add(rep);
assertErrror(response, "'Private RSA Key' is required"); assertErrror(response, "'Private RSA Key' is required");
@ -185,7 +186,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest {
KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048); KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
Certificate invalidCertificate = CertificateUtils.generateV1SelfSignedCertificate(KeyUtils.generateRsaKeyPair(2048), "test"); Certificate invalidCertificate = CertificateUtils.generateV1SelfSignedCertificate(KeyUtils.generateRsaKeyPair(2048), "test");
ComponentRepresentation rep = createRep("invalid", RsaKeyProviderFactory.ID); ComponentRepresentation rep = createRep("invalid", ImportedRsaKeyProviderFactory.ID);
rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate())); rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate()));
rep.getConfig().putSingle(Attributes.CERTIFICATE_KEY, "nonsense"); rep.getConfig().putSingle(Attributes.CERTIFICATE_KEY, "nonsense");

View file

@ -23,6 +23,7 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.jose.jws.AlgorithmType;
import org.keycloak.keys.JavaKeystoreKeyProviderFactory; import org.keycloak.keys.JavaKeystoreKeyProviderFactory;
import org.keycloak.keys.KeyMetadata; import org.keycloak.keys.KeyMetadata;
import org.keycloak.keys.KeyProvider; import org.keycloak.keys.KeyProvider;
@ -103,7 +104,7 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0); KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0);
assertEquals(id, key.getProviderId()); assertEquals(id, key.getProviderId());
assertEquals(KeyMetadata.Type.RSA.name(), key.getType()); assertEquals(AlgorithmType.RSA.name(), key.getType());
assertEquals(priority, key.getProviderPriority()); assertEquals(priority, key.getProviderPriority());
assertEquals(PUBLIC_KEY, key.getPublicKey()); assertEquals(PUBLIC_KEY, key.getPublicKey());
assertEquals(CERTIFICATE, key.getCertificate()); assertEquals(CERTIFICATE, key.getCertificate());

View file

@ -28,14 +28,13 @@ import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.PemUtils; import org.keycloak.common.util.PemUtils;
import org.keycloak.keys.Attributes; import org.keycloak.keys.Attributes;
import org.keycloak.keys.GeneratedHmacKeyProviderFactory;
import org.keycloak.keys.KeyProvider; import org.keycloak.keys.KeyProvider;
import org.keycloak.keys.RsaKeyProviderFactory; import org.keycloak.keys.ImportedRsaKeyProviderFactory;
import org.keycloak.representations.UserInfo;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.KeysMetadataRepresentation; import org.keycloak.representations.idm.KeysMetadataRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.AppPage;
@ -237,7 +236,7 @@ public class KeyRotationTest extends AbstractKeycloakTest {
ComponentRepresentation rep = new ComponentRepresentation(); ComponentRepresentation rep = new ComponentRepresentation();
rep.setName("mycomponent"); rep.setName("mycomponent");
rep.setParentId("test"); rep.setParentId("test");
rep.setProviderId(RsaKeyProviderFactory.ID); rep.setProviderId(ImportedRsaKeyProviderFactory.ID);
rep.setProviderType(KeyProvider.class.getName()); rep.setProviderType(KeyProvider.class.getName());
org.keycloak.common.util.MultivaluedHashMap config = new org.keycloak.common.util.MultivaluedHashMap(); org.keycloak.common.util.MultivaluedHashMap config = new org.keycloak.common.util.MultivaluedHashMap();
@ -247,6 +246,18 @@ public class KeyRotationTest extends AbstractKeycloakTest {
adminClient.realm("test").components().add(rep); adminClient.realm("test").components().add(rep);
rep = new ComponentRepresentation();
rep.setName("mycomponent2");
rep.setParentId("test");
rep.setProviderId(GeneratedHmacKeyProviderFactory.ID);
rep.setProviderType(KeyProvider.class.getName());
config = new org.keycloak.common.util.MultivaluedHashMap();
config.addFirst("priority", priority);
rep.setConfig(config);
adminClient.realm("test").components().add(rep);
return publicKey; return publicKey;
} }
@ -259,14 +270,17 @@ public class KeyRotationTest extends AbstractKeycloakTest {
} }
private void dropKeys(String priority) { private void dropKeys(String priority) {
int r = 0;
for (ComponentRepresentation c : adminClient.realm("test").components().query("test", KeyProvider.class.getName())) { for (ComponentRepresentation c : adminClient.realm("test").components().query("test", KeyProvider.class.getName())) {
if (c.getConfig().getFirst("priority").equals(priority)) { if (c.getConfig().getFirst("priority").equals(priority)) {
adminClient.realm("test").components().component(c.getId()).remove(); adminClient.realm("test").components().component(c.getId()).remove();
return; r++;
} }
} }
if (r != 2) {
throw new RuntimeException("Failed to find keys1"); throw new RuntimeException("Failed to find keys1");
} }
}
private void assertUserInfo(String token, int expectedStatus) { private void assertUserInfo(String token, int expectedStatus) {
Response userInfoResponse = UserInfoClientUtil.executeUserInfoRequest_getMethod(ClientBuilder.newClient(), token); Response userInfoResponse = UserInfoClientUtil.executeUserInfoRequest_getMethod(ClientBuilder.newClient(), token);

View file

@ -0,0 +1,92 @@
/*
* 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.testsuite.model;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.KeyPair;
import static org.junit.Assert.assertEquals;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@Ignore
public class SimplePerfTest {
@Rule
public WebRule webRule = new WebRule(this);
@WebResource
public WebDriver driver;
public static final String PORT = "8080";
public static final int WARMUP = 1000;
public static final int COUNT = 10000;
@Test
public void simplePerf() {
// Warm-up
for (int i = 0; i < WARMUP; i++) {
doLoginLogout(PORT);
}
long start = System.currentTimeMillis();
long s = start;
for (int i = 0; i < COUNT; i++) {
doLoginLogout(PORT);
if (i % 100 == 0) {
System.out.println(i + " " + (System.currentTimeMillis() - s) + " ms");
}
s = System.currentTimeMillis();
}
System.out.println("");
System.out.println("Average: " + ((System.currentTimeMillis() - start) / COUNT) + " ms");
System.out.println("Total: " + ((System.currentTimeMillis() - start)) + " ms");
}
private void doLoginLogout(String port) {
driver.navigate().to("http://localhost:" + port + "/auth/realms/master/account/");
driver.findElement(By.id("username")).sendKeys("admin");
driver.findElement(By.id("password")).sendKeys("admin");
driver.findElement(By.name("login")).click();
assertEquals("http://localhost:" + port + "/auth/realms/master/account/", driver.getCurrentUrl());
driver.findElement(By.linkText("Sign Out")).click();
}
}

View file

@ -39,6 +39,7 @@ sslRequired.option.all=all requests
sslRequired.option.external=external requests sslRequired.option.external=external requests
sslRequired.option.none=none sslRequired.option.none=none
sslRequired.tooltip=Is HTTPS required? 'None' means HTTPS is not required for any client IP address. 'External requests' means localhost and private IP addresses can access without HTTPS. 'All requests' means HTTPS is required for all IP addresses. sslRequired.tooltip=Is HTTPS required? 'None' means HTTPS is not required for any client IP address. 'External requests' means localhost and private IP addresses can access without HTTPS. 'All requests' means HTTPS is required for all IP addresses.
publicKeys=Public keys
publicKey=Public key publicKey=Public key
privateKey=Private key privateKey=Private key
gen-new-keys=Generate new keys gen-new-keys=Generate new keys

View file

@ -1126,6 +1126,14 @@ module.controller('RealmKeysProvidersCtrl', function($scope, Realm, realm, $http
type: 'org.keycloak.keys.KeyProvider' type: 'org.keycloak.keys.KeyProvider'
}, function(data) { }, function(data) {
$scope.instances = data; $scope.instances = data;
for (var i = 0; i < $scope.instances.length; i++) {
for (var j = 0; j < $scope.providers.length; j++) {
if ($scope.providers[j].id === $scope.instances[i].providerId) {
$scope.instances[i].provider = $scope.providers[j];
}
}
}
}); });
$scope.addProvider = function(provider) { $scope.addProvider = function(provider) {

View file

@ -27,28 +27,26 @@
<table class="table table-striped table-bordered"> <table class="table table-striped table-bordered">
<thead> <thead>
<tr> <tr>
<th>{{:: 'status' | translate}}</th>
<th>{{:: 'type' | translate}}</th> <th>{{:: 'type' | translate}}</th>
<th>{{:: 'status' | translate}}</th>
<th>{{:: 'kid' | translate}}</th> <th>{{:: 'kid' | translate}}</th>
<th>{{:: 'priority' | translate}}</th> <th>{{:: 'priority' | translate}}</th>
<th>{{:: 'provider' | translate}}</th> <th>{{:: 'provider' | translate}}</th>
<th>{{:: 'publicKey' | translate}}</th> <th colspan="2">{{:: 'publicKeys' | translate}}</th>
<th>{{:: 'certificate' | translate}}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr ng-repeat="key in keys"> <tr ng-repeat="key in keys">
<td>{{key.status}}</td>
<td>{{key.type}}</td> <td>{{key.type}}</td>
<td>{{key.status}}</td>
<td>{{key.kid}}</td> <td>{{key.kid}}</td>
<td>{{key.providerPriority}}</td> <td>{{key.providerPriority}}</td>
<td><a href="#/realms/{{realm.realm}}/keys/providers/{{key.provider.providerId}}/{{key.provider.id}}">{{key.provider.name}}</a></td> <td><a href="#/realms/{{realm.realm}}/keys/providers/{{key.provider.providerId}}/{{key.provider.id}}">{{key.provider.name}}</a></td>
<td data-ng-show="key.publicKey" class="kc-action-cell" data-ng-click="viewKey(key.publicKey)">{{:: 'view' | translate}}</td> <td data-ng-show="key.type === 'RSA'" class="kc-action-cell" data-ng-click="viewKey(key.publicKey)">{{:: 'publicKey' | translate}}</td>
<td data-ng-hide="key.publicKey"></td> <td data-ng-show="key.type === 'RSA'" class="kc-action-cell" data-ng-click="viewKey(key.certificate)">{{:: 'certificate' | translate}}</td>
<td data-ng-show="key.certificate" class="kc-action-cell" data-ng-click="viewKey(key.certificate)">{{:: 'view' | translate}}</td> <td data-ng-show="key.type !== 'RSA'" colspan="2"></td>
<td data-ng-hide="key.certificate"></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View file

@ -27,7 +27,7 @@
<table class="table table-striped table-bordered"> <table class="table table-striped table-bordered">
<thead> <thead>
<tr ng-show="providers.length > 0 && access.manageRealm"> <tr ng-show="providers.length > 0 && access.manageRealm">
<th colspan="6" class="kc-table-actions"> <th colspan="7" class="kc-table-actions">
<div class="pull-right"> <div class="pull-right">
<div> <div>
<select class="form-control" ng-model="selectedProvider" <select class="form-control" ng-model="selectedProvider"
@ -40,6 +40,7 @@
</th> </th>
</tr> </tr>
<tr data-ng-show="instances && instances.length > 0"> <tr data-ng-show="instances && instances.length > 0">
<th>{{:: 'type' | translate}}</th>
<th>{{:: 'name' | translate}}</th> <th>{{:: 'name' | translate}}</th>
<th>{{:: 'id' | translate}}</th> <th>{{:: 'id' | translate}}</th>
<th>{{:: 'provider' | translate}}</th> <th>{{:: 'provider' | translate}}</th>
@ -49,6 +50,7 @@
</thead> </thead>
<tbody> <tbody>
<tr ng-repeat="instance in instances"> <tr ng-repeat="instance in instances">
<td>{{instance.provider.metadata.algorithmType}}</td>
<td>{{instance.name}}</td> <td>{{instance.name}}</td>
<td><a href="#/realms/{{realm.realm}}/keys/providers/{{instance.providerId}}/{{instance.id}}">{{instance.id}}</a></td> <td><a href="#/realms/{{realm.realm}}/keys/providers/{{instance.providerId}}/{{instance.id}}">{{instance.id}}</a></td>
<td>{{instance.providerId}}</td> <td>{{instance.providerId}}</td>

View file

@ -13,8 +13,7 @@
<th>{{:: 'type' | translate}}</th> <th>{{:: 'type' | translate}}</th>
<th>{{:: 'kid' | translate}}</th> <th>{{:: 'kid' | translate}}</th>
<th>{{:: 'provider' | translate}}</th> <th>{{:: 'provider' | translate}}</th>
<th>{{:: 'publicKey' | translate}}</th> <th colspan="2">{{:: 'publicKeys' | translate}}</th>
<th>{{:: 'certificate' | translate}}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -23,11 +22,10 @@
<td>{{key.kid}}</td> <td>{{key.kid}}</td>
<td><a href="#/realms/{{realm.realm}}/keys/providers/{{key.provider.providerId}}/{{key.provider.id}}">{{key.provider.name}}</a></td> <td><a href="#/realms/{{realm.realm}}/keys/providers/{{key.provider.providerId}}/{{key.provider.id}}">{{key.provider.name}}</a></td>
<td data-ng-show="key.publicKey" class="kc-action-cell" data-ng-click="viewKey(key.publicKey)">{{:: 'view' | translate}}</td> <td data-ng-show="key.type === 'RSA'" class="kc-action-cell" data-ng-click="viewKey(key.publicKey)">{{:: 'publicKey' | translate}}</td>
<td data-ng-hide="key.publicKey"></td> <td data-ng-show="key.type === 'RSA'" class="kc-action-cell" data-ng-click="viewKey(key.certificate)">{{:: 'certificate' | translate}}</td>
<td data-ng-show="key.certificate" class="kc-action-cell" data-ng-click="viewKey(key.certificate)">{{:: 'view' | translate}}</td> <td data-ng-show="key.type !== 'RSA'" colspan="2"></td>
<td data-ng-hide="key.certificate"></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>