KEYCLOAK-7701 Refactor key providers to support additional algorithms

This commit is contained in:
stianst 2018-06-22 16:05:42 +02:00 committed by Stian Thorgersen
parent a5d155a35a
commit 3c5027de3c
53 changed files with 832 additions and 815 deletions

View file

@ -14,22 +14,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.crypto;
package org.keycloak.keys;
public interface Algorithm {
import org.keycloak.jose.jws.AlgorithmType;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface HmacKeyProvider extends SecretKeyProvider {
default AlgorithmType getType() {
return AlgorithmType.HMAC;
}
default String getJavaAlgorithmName() {
return "HmacSHA256";
}
String HS256 = "HS256";
String HS384 = "HS384";
String HS512 = "HS512";
String RS256 = "RS256";
String RS384 = "RS384";
String RS512 = "RS512";
String ES256 = "ES256";
String ES384 = "ES384";
String ES512 = "ES512";
String AES = "AES";
}

View file

@ -0,0 +1,42 @@
/*
* 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.crypto;
public class JavaAlgorithm {
public static String getJavaAlgorithm(String algorithm) {
switch (algorithm) {
case Algorithm.RS256:
return "SHA256withRSA";
case Algorithm.RS384:
return "SHA384withRSA";
case Algorithm.RS512:
return "SHA512withRSA";
case Algorithm.HS256:
return "HMACSHA256";
case Algorithm.HS384:
return "HMACSHA384";
case Algorithm.HS512:
return "HMACSHA512";
case Algorithm.AES:
return "AES";
default:
throw new IllegalArgumentException("Unkown algorithm " + algorithm);
}
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* 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");
@ -14,21 +14,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.crypto;
package org.keycloak.keys;
public enum KeyStatus {
import org.keycloak.jose.jws.AlgorithmType;
ACTIVE, PASSIVE, DISABLED;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface AesKeyProvider extends SecretKeyProvider {
default AlgorithmType getType() {
return AlgorithmType.AES;
public static KeyStatus from(boolean active, boolean enabled) {
if (!enabled) {
return KeyStatus.DISABLED;
} else {
return active ? KeyStatus.ACTIVE : KeyStatus.PASSIVE;
}
}
default String getJavaAlgorithmName() {
return "AES";
public boolean isActive() {
return this.equals(ACTIVE);
}
public boolean isEnabled() {
return this.equals(ACTIVE) || this.equals(PASSIVE);
}
}

View file

@ -14,22 +14,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.crypto;
package org.keycloak.keys;
public interface KeyType {
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);
}
String EC = "EC";
String RSA = "RSA";
String OCT = "OCT";
}

View file

@ -14,22 +14,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.crypto;
package org.keycloak.keys;
public enum KeyUse {
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<HmacKeyProvider> {
@Override
default Map<String, Object> getTypeMetadata() {
return Collections.singletonMap("algorithmType", AlgorithmType.HMAC);
}
SIG,
ENC
}

View file

@ -0,0 +1,135 @@
/*
* 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.crypto;
import javax.crypto.SecretKey;
import java.security.Key;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class KeyWrapper {
private String providerId;
private long providerPriority;
private String kid;
private Set<String> algorithms;
private String type;
private KeyUse use;
private KeyStatus status;
private SecretKey secretKey;
private Key signKey;
private Key verifyKey;
private X509Certificate certificate;
public String getProviderId() {
return providerId;
}
public void setProviderId(String providerId) {
this.providerId = providerId;
}
public long getProviderPriority() {
return providerPriority;
}
public void setProviderPriority(long providerPriority) {
this.providerPriority = providerPriority;
}
public String getKid() {
return kid;
}
public void setKid(String kid) {
this.kid = kid;
}
public Set<String> getAlgorithms() {
return algorithms;
}
public void setAlgorithms(String... algorithms) {
this.algorithms = new HashSet<>();
for (String a : algorithms) {
this.algorithms.add(a);
}
}
public void setAlgorithms(Set<String> algorithms) {
this.algorithms = algorithms;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public KeyUse getUse() {
return use;
}
public void setUse(KeyUse use) {
this.use = use;
}
public KeyStatus getStatus() {
return status;
}
public void setStatus(KeyStatus status) {
this.status = status;
}
public SecretKey getSecretKey() {
return secretKey;
}
public void setSecretKey(SecretKey secretKey) {
this.secretKey = secretKey;
}
public Key getSignKey() {
return signKey;
}
public void setSignKey(Key signKey) {
this.signKey = signKey;
}
public Key getVerifyKey() {
return verifyKey;
}
public void setVerifyKey(Key verifyKey) {
this.verifyKey = verifyKey;
}
public X509Certificate getCertificate() {
return certificate;
}
public void setCertificate(X509Certificate certificate) {
this.certificate = certificate;
}
}

View file

@ -19,6 +19,7 @@ package org.keycloak.representations.idm;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -54,6 +55,7 @@ public class KeysMetadataRepresentation {
private String status;
private String type;
private Set<String> algorithms;
private String publicKey;
private String certificate;
@ -98,6 +100,14 @@ public class KeysMetadataRepresentation {
this.type = type;
}
public Set<String> getAlgorithms() {
return algorithms;
}
public void setAlgorithms(Set<String> algorithms) {
this.algorithms = algorithms;
}
public String getPublicKey() {
return publicKey;
}

View file

@ -1,34 +0,0 @@
/*
* Copyright 2017 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.util.Collections;
import java.util.Map;
import org.keycloak.jose.jws.AlgorithmType;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface AesKeyProviderFactory extends KeyProviderFactory<AesKeyProvider> {
@Override
default Map<String, Object> getTypeMetadata() {
return Collections.singletonMap("algorithmType", AlgorithmType.AES);
}
}

View file

@ -17,6 +17,7 @@
package org.keycloak.keys;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.jose.jws.AlgorithmType;
import org.keycloak.provider.Provider;
@ -28,26 +29,15 @@ import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface KeyProvider<T extends KeyMetadata> extends Provider {
public interface KeyProvider extends Provider {
/**
* Returns the algorithm type the keys can be used for
*
* Returns the key
* @return
*/
AlgorithmType getType();
List<KeyWrapper> getKeys();
/**
* Return the KID for the active keypair, or <code>null</code> if no active key is available.
*
* @return
*/
String getKid();
/**
* Return metadata about all keypairs held by the provider
* @return
*/
List<T> getKeyMetadata();
default void close() {
}
}

View file

@ -17,9 +17,11 @@
package org.keycloak.keys;
import org.keycloak.Config;
import org.keycloak.component.ComponentFactory;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -28,4 +30,16 @@ public interface KeyProviderFactory<T extends KeyProvider> extends ComponentFact
T create(KeycloakSession session, ComponentModel model);
@Override
default void init(Config.Scope config) {
}
@Override
default void postInit(KeycloakSessionFactory factory) {
}
@Override
default void close() {
}
}

View file

@ -1,60 +0,0 @@
/*
* 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

@ -1,50 +0,0 @@
/*
* Copyright 2017 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 javax.crypto.SecretKey;
/**
* Base for secret key providers (HMAC, AES)
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface SecretKeyProvider extends KeyProvider<SecretKeyMetadata> {
/**
* 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);
/**
* Return name of Java (JCA) algorithm of the key. For example: HmacSHA256
* @return
*/
String getJavaAlgorithmName();
}

View file

@ -17,21 +17,19 @@
package org.keycloak.keys;
import org.keycloak.crypto.KeyStatus;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public abstract class KeyMetadata {
public enum Status {
ACTIVE, PASSIVE, DISABLED
}
private String providerId;
private long providerPriority;
private String kid;
private Status status;
private KeyStatus status;
public String getProviderId() {
return providerId;
@ -57,11 +55,11 @@ public abstract class KeyMetadata {
this.kid = kid;
}
public Status getStatus() {
public KeyStatus getStatus() {
return status;
}
public void setStatus(Status status) {
public void setStatus(KeyStatus status) {
this.status = status;
}

View file

@ -17,6 +17,8 @@
package org.keycloak.models;
import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.keys.SecretKeyMetadata;
import org.keycloak.keys.RsaKeyMetadata;
@ -32,25 +34,43 @@ import java.util.List;
*/
public interface KeyManager {
KeyWrapper getActiveKey(RealmModel realm, KeyUse use, String algorithm);
KeyWrapper getKey(RealmModel realm, String kid, KeyUse use, String algorithm);
List<KeyWrapper> getKeys(RealmModel realm);
List<KeyWrapper> getKeys(RealmModel realm, KeyUse use, String algorithm);
@Deprecated
ActiveRsaKey getActiveRsaKey(RealmModel realm);
@Deprecated
PublicKey getRsaPublicKey(RealmModel realm, String kid);
@Deprecated
Certificate getRsaCertificate(RealmModel realm, String kid);
List<RsaKeyMetadata> getRsaKeys(RealmModel realm, boolean includeDisabled);
@Deprecated
List<RsaKeyMetadata> getRsaKeys(RealmModel realm);
@Deprecated
ActiveHmacKey getActiveHmacKey(RealmModel realm);
@Deprecated
SecretKey getHmacSecretKey(RealmModel realm, String kid);
List<SecretKeyMetadata> getHmacKeys(RealmModel realm, boolean includeDisabled);
@Deprecated
List<SecretKeyMetadata> getHmacKeys(RealmModel realm);
@Deprecated
ActiveAesKey getActiveAesKey(RealmModel realm);
@Deprecated
SecretKey getAesSecretKey(RealmModel realm, String kid);
List<SecretKeyMetadata> getAesKeys(RealmModel realm, boolean includeDisabled);
@Deprecated
List<SecretKeyMetadata> getAesKeys(RealmModel realm);
class ActiveRsaKey {
private final String kid;

View file

@ -17,33 +17,25 @@
package org.keycloak.broker.saml;
import org.jboss.logging.Logger;
import org.keycloak.broker.provider.AbstractIdentityProvider;
import org.keycloak.broker.provider.AuthenticationRequest;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.provider.IdentityProviderDataMarshaller;
import org.keycloak.broker.provider.*;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.common.util.PemUtils;
import org.keycloak.crypto.KeyStatus;
import org.keycloak.dom.saml.v2.assertion.AssertionType;
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
import org.keycloak.dom.saml.v2.assertion.NameIDType;
import org.keycloak.dom.saml.v2.assertion.SubjectType;
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
import org.keycloak.dom.saml.v2.protocol.ResponseType;
import org.keycloak.events.EventBuilder;
import org.keycloak.keys.RsaKeyMetadata;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.*;
import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder;
import org.keycloak.saml.SAML2AuthnRequestBuilder;
import org.keycloak.saml.SAML2LogoutRequestBuilder;
import org.keycloak.saml.SAML2NameIDPolicyBuilder;
import org.keycloak.saml.SPMetadataDescriptor;
import org.keycloak.saml.SignatureAlgorithm;
import org.keycloak.saml.*;
import org.keycloak.saml.common.constants.GeneralConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
import org.keycloak.sessions.AuthenticationSessionModel;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@ -52,11 +44,6 @@ import javax.ws.rs.core.UriInfo;
import java.security.KeyPair;
import java.util.Set;
import java.util.TreeSet;
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
import org.keycloak.keys.KeyMetadata;
import org.keycloak.keys.KeyMetadata.Status;
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
import org.keycloak.sessions.AuthenticationSessionModel;
/**
* @author Pedro Igor
@ -246,12 +233,12 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
StringBuilder encryptionKeysString = new StringBuilder();
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())
: (o1.getStatus() == KeyMetadata.Status.PASSIVE ? 1 : -1));
keys.addAll(session.keys().getRsaKeys(realm, false));
: (o1.getStatus() == KeyStatus.PASSIVE ? 1 : -1));
keys.addAll(session.keys().getRsaKeys(realm));
for (RsaKeyMetadata key : keys) {
addKeyInfo(signingKeysString, key, KeyTypes.SIGNING.value());
if (key.getStatus() == Status.ACTIVE) {
if (key.getStatus() == KeyStatus.ACTIVE) {
addKeyInfo(encryptionKeysString, key, KeyTypes.ENCRYPTION.value());
}
}

View file

@ -17,13 +17,12 @@
package org.keycloak.keys;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.component.ComponentModel;
import org.keycloak.jose.jws.AlgorithmType;
import org.keycloak.crypto.*;
import org.keycloak.models.RealmModel;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.List;
@ -31,110 +30,49 @@ import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public abstract class AbstractRsaKeyProvider implements RsaKeyProvider {
public abstract class AbstractRsaKeyProvider implements KeyProvider {
private final boolean enabled;
private final boolean active;
private final KeyStatus status;
private final ComponentModel model;
private final Keys keys;
private final KeyWrapper key;
public AbstractRsaKeyProvider(RealmModel realm, ComponentModel model) {
this.model = model;
this.status = KeyStatus.from(model.get(Attributes.ACTIVE_KEY, true), model.get(Attributes.ENABLED_KEY, true));
this.enabled = model.get(Attributes.ENABLED_KEY, true);
this.active = model.get(Attributes.ACTIVE_KEY, true);
if (model.hasNote(Keys.class.getName())) {
keys = model.getNote(Keys.class.getName());
if (model.hasNote(KeyWrapper.class.getName())) {
key = model.getNote(KeyWrapper.class.getName());
} else {
keys = loadKeys(realm, model);
model.setNote(Keys.class.getName(), keys);
key = loadKey(realm, model);
model.setNote(KeyWrapper.class.getName(), key);
}
}
protected abstract Keys loadKeys(RealmModel realm, ComponentModel model);
protected abstract KeyWrapper loadKey(RealmModel realm, ComponentModel model);
@Override
public final String getKid() {
return isActive() ? keys.getKid() : null;
public List<KeyWrapper> getKeys() {
return Collections.singletonList(key);
}
@Override
public final PrivateKey getPrivateKey() {
return isActive() ? keys.getKeyPair().getPrivate() : null;
}
protected KeyWrapper createKeyWrapper(KeyPair keyPair, X509Certificate certificate) {
KeyWrapper key = new KeyWrapper();
@Override
public final PublicKey getPublicKey(String kid) {
return isEnabled() && kid.equals(keys.getKid()) ? keys.getKeyPair().getPublic() : null;
}
key.setProviderId(model.getId());
key.setProviderPriority(model.get("priority", 0l));
@Override
public X509Certificate getCertificate(String kid) {
return isEnabled() && kid.equals(keys.getKid()) ? keys.getCertificate() : null;
}
key.setKid(KeyUtils.createKeyId(keyPair.getPublic()));
key.setUse(KeyUse.SIG);
key.setType(KeyType.RSA);
key.setAlgorithms(Algorithm.RS256, Algorithm.RS384, Algorithm.RS512);
key.setStatus(status);
key.setSignKey(keyPair.getPrivate());
key.setVerifyKey(keyPair.getPublic());
key.setCertificate(certificate);
@Override
public final List<RsaKeyMetadata> getKeyMetadata() {
String kid = keys.getKid();
PublicKey publicKey = keys.getKeyPair().getPublic();
if (kid != null && publicKey != null) {
RsaKeyMetadata k = new RsaKeyMetadata();
k.setProviderId(model.getId());
k.setProviderPriority(model.get(Attributes.PRIORITY_KEY, 0l));
k.setKid(kid);
if (isActive()) {
k.setStatus(KeyMetadata.Status.ACTIVE);
} else if (isEnabled()) {
k.setStatus(KeyMetadata.Status.PASSIVE);
} else {
k.setStatus(KeyMetadata.Status.DISABLED);
}
k.setPublicKey(publicKey);
k.setCertificate(keys.getCertificate());
return Collections.singletonList(k);
} else {
return Collections.emptyList();
}
}
@Override
public void close() {
}
private boolean isEnabled() {
return keys != null && enabled;
}
private boolean isActive() {
return isEnabled() && active;
}
public static class Keys {
private String kid;
private KeyPair keyPair;
private X509Certificate certificate;
public Keys(String kid, KeyPair keyPair, X509Certificate certificate) {
this.kid = kid;
this.keyPair = keyPair;
this.certificate = certificate;
}
public String getKid() {
return kid;
}
public KeyPair getKeyPair() {
return keyPair;
}
public X509Certificate getCertificate() {
return certificate;
}
return key;
}
}

View file

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

View file

@ -19,20 +19,19 @@ package org.keycloak.keys;
import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.jose.jws.AlgorithmType;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ProviderFactory;
import javax.crypto.SecretKey;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.*;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -49,204 +48,172 @@ public class DefaultKeyManager implements KeyManager {
}
@Override
public KeyWrapper getActiveKey(RealmModel realm, KeyUse use, String algorithm) {
for (KeyProvider p : getProviders(realm)) {
for (KeyWrapper key : p .getKeys()) {
if (key.getStatus().isActive() && matches(key, use, algorithm)) {
if (logger.isTraceEnabled()) {
logger.tracev("Active key found: realm={0} kid={1} algorithm={2}", realm.getName(), key.getKid(), algorithm);
}
return key;
}
}
}
throw new RuntimeException("Failed to find key: realm=" + realm.getName() + " algorithm=" + algorithm);
}
@Override
public KeyWrapper getKey(RealmModel realm, String kid, KeyUse use, String algorithm) {
if (kid == null) {
logger.warnv("kid is null, can't find public key", realm.getName(), kid);
return null;
}
for (KeyProvider p : getProviders(realm)) {
for (KeyWrapper key : p.getKeys()) {
if (key.getKid().equals(kid) && key.getStatus().isEnabled() && matches(key, use, algorithm)) {
if (logger.isTraceEnabled()) {
logger.tracev("Active key realm={0} kid={1} algorithm={2}", realm.getName(), key.getKid(), algorithm);
}
return key;
}
}
}
if (logger.isTraceEnabled()) {
logger.tracev("Failed to find public key realm={0} kid={1} algorithm={2}", realm.getName(), kid, algorithm);
}
return null;
}
@Override
public List<KeyWrapper> getKeys(RealmModel realm, KeyUse use, String algorithm) {
List<KeyWrapper> keys = new LinkedList<>();
for (KeyProvider p : getProviders(realm)) {
for (KeyWrapper key : p .getKeys()) {
if (key.getStatus().isEnabled() && matches(key, use, algorithm)) {
keys.add(key);
}
}
}
return keys;
}
@Override
public List<KeyWrapper> getKeys(RealmModel realm) {
List<KeyWrapper> keys = new LinkedList<>();
for (KeyProvider p : getProviders(realm)) {
for (KeyWrapper key : p .getKeys()) {
keys.add(key);
}
}
return keys;
}
@Override
@Deprecated
public ActiveRsaKey getActiveRsaKey(RealmModel realm) {
for (KeyProvider p : getProviders(realm)) {
if (p.getType().equals(AlgorithmType.RSA)) {
RsaKeyProvider r = (RsaKeyProvider) p;
if (r.getKid() != null && r.getPrivateKey() != null) {
if (logger.isTraceEnabled()) {
logger.tracev("Active key realm={0} kid={1}", realm.getName(), p.getKid());
}
String kid = p.getKid();
return new ActiveRsaKey(kid, r.getPrivateKey(), r.getPublicKey(kid), r.getCertificate(kid));
}
}
}
throw new RuntimeException("Failed to get RSA keys");
KeyWrapper key = getActiveKey(realm, KeyUse.SIG, Algorithm.RS256);
return new ActiveRsaKey(key.getKid(), (PrivateKey) key.getSignKey(), (PublicKey) key.getVerifyKey(), key.getCertificate());
}
@Override
@Deprecated
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");
KeyWrapper key = getActiveKey(realm, KeyUse.SIG, Algorithm.HS256);
return new ActiveHmacKey(key.getKid(), key.getSecretKey());
}
@Override
@Deprecated
public ActiveAesKey getActiveAesKey(RealmModel realm) {
for (KeyProvider p : getProviders(realm)) {
if (p.getType().equals(AlgorithmType.AES)) {
AesKeyProvider h = (AesKeyProvider) p;
if (h.getKid() != null && h.getSecretKey() != null) {
if (logger.isTraceEnabled()) {
logger.tracev("Active AES Key realm={0} kid={1}", realm.getName(), p.getKid());
}
String kid = p.getKid();
return new ActiveAesKey(kid, h.getSecretKey());
}
}
}
throw new RuntimeException("Failed to get keys");
KeyWrapper key = getActiveKey(realm, KeyUse.ENC, Algorithm.AES);
return new ActiveAesKey(key.getKid(), key.getSecretKey());
}
@Override
@Deprecated
public PublicKey getRsaPublicKey(RealmModel realm, String kid) {
if (kid == null) {
logger.warnv("KID is null, can't find public key", realm.getName(), kid);
return null;
}
for (KeyProvider p : getProviders(realm)) {
if (p.getType().equals(AlgorithmType.RSA)) {
RsaKeyProvider r = (RsaKeyProvider) p;
PublicKey publicKey = r.getPublicKey(kid);
if (publicKey != null) {
if (logger.isTraceEnabled()) {
logger.tracev("Found public key realm={0} kid={1}", realm.getName(), kid);
}
return publicKey;
}
}
}
if (logger.isTraceEnabled()) {
logger.tracev("Failed to find public key realm={0} kid={1}", realm.getName(), kid);
}
return null;
KeyWrapper key = getKey(realm, kid, KeyUse.SIG, Algorithm.RS256);
return key != null ? (PublicKey) key.getVerifyKey() : null;
}
@Override
@Deprecated
public Certificate getRsaCertificate(RealmModel realm, String kid) {
if (kid == null) {
logger.warnv("KID is null, can't find public key", realm.getName(), kid);
return null;
}
for (KeyProvider p : getProviders(realm)) {
if (p.getType().equals(AlgorithmType.RSA)) {
RsaKeyProvider r = (RsaKeyProvider) p;
Certificate certificate = r.getCertificate(kid);
if (certificate != null) {
if (logger.isTraceEnabled()) {
logger.tracev("Found certificate realm={0} kid={1}", realm.getName(), kid);
}
return certificate;
}
}
}
if (logger.isTraceEnabled()) {
logger.tracev("Failed to find certificate realm={0} kid={1}", realm.getName(), kid);
}
return null;
KeyWrapper key = getKey(realm, kid, KeyUse.SIG, Algorithm.RS256);
return key != null ? key.getCertificate() : null;
}
@Override
@Deprecated
public SecretKey getHmacSecretKey(RealmModel realm, String kid) {
if (kid == null) {
logger.warnv("KID is null, can't find secret key", realm.getName(), kid);
return null;
}
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;
KeyWrapper key = getKey(realm, kid, KeyUse.SIG, Algorithm.HS256);
return key != null ? key.getSecretKey() : null;
}
@Override
@Deprecated
public SecretKey getAesSecretKey(RealmModel realm, String kid) {
if (kid == null) {
logger.warnv("KID is null, can't find aes key", realm.getName(), kid);
return null;
}
for (KeyProvider p : getProviders(realm)) {
if (p.getType().equals(AlgorithmType.AES)) {
AesKeyProvider h = (AesKeyProvider) p;
SecretKey s = h.getSecretKey(kid);
if (s != null) {
if (logger.isTraceEnabled()) {
logger.tracev("Found AES key realm={0} kid={1}", realm.getName(), kid);
}
return s;
}
}
}
if (logger.isTraceEnabled()) {
logger.tracev("Failed to find AES key realm={0} kid={1}", realm.getName(), kid);
}
return null;
KeyWrapper key = getKey(realm, kid, KeyUse.ENC, Algorithm.AES);
return key.getSecretKey();
}
@Override
public List<RsaKeyMetadata> getRsaKeys(RealmModel realm, boolean includeDisabled) {
@Deprecated
public List<RsaKeyMetadata> getRsaKeys(RealmModel realm) {
List<RsaKeyMetadata> keys = new LinkedList<>();
for (KeyProvider p : getProviders(realm)) {
if (p instanceof RsaKeyProvider) {
if (includeDisabled) {
keys.addAll(p.getKeyMetadata());
} else {
List<RsaKeyMetadata> metadata = p.getKeyMetadata();
metadata.stream().filter(k -> k.getStatus() != KeyMetadata.Status.DISABLED).forEach(k -> keys.add(k));
}
}
for (KeyWrapper key : getKeys(realm, KeyUse.SIG, Algorithm.RS256)) {
RsaKeyMetadata m = new RsaKeyMetadata();
m.setCertificate(key.getCertificate());
m.setPublicKey((PublicKey) key.getVerifyKey());
m.setKid(key.getKid());
m.setProviderId(key.getProviderId());
m.setProviderPriority(key.getProviderPriority());
m.setStatus(key.getStatus());
keys.add(m);
}
return keys;
}
@Override
public List<SecretKeyMetadata> getHmacKeys(RealmModel realm, boolean includeDisabled) {
public List<SecretKeyMetadata> getHmacKeys(RealmModel realm) {
List<SecretKeyMetadata> keys = new LinkedList<>();
for (KeyProvider p : getProviders(realm)) {
if (p instanceof HmacKeyProvider) {
if (includeDisabled) {
keys.addAll(p.getKeyMetadata());
} else {
List<SecretKeyMetadata> metadata = p.getKeyMetadata();
metadata.stream().filter(k -> k.getStatus() != KeyMetadata.Status.DISABLED).forEach(k -> keys.add(k));
}
}
for (KeyWrapper key : getKeys(realm, KeyUse.SIG, Algorithm.HS256)) {
SecretKeyMetadata m = new SecretKeyMetadata();
m.setKid(key.getKid());
m.setProviderId(key.getProviderId());
m.setProviderPriority(key.getProviderPriority());
m.setStatus(key.getStatus());
keys.add(m);
}
return keys;
}
@Override
public List<SecretKeyMetadata> getAesKeys(RealmModel realm, boolean includeDisabled) {
public List<SecretKeyMetadata> getAesKeys(RealmModel realm) {
List<SecretKeyMetadata> keys = new LinkedList<>();
for (KeyProvider p : getProviders(realm)) {
if (p instanceof AesKeyProvider) {
if (includeDisabled) {
keys.addAll(p.getKeyMetadata());
} else {
List<SecretKeyMetadata> metadata = p.getKeyMetadata();
metadata.stream().filter(k -> k.getStatus() != KeyMetadata.Status.DISABLED).forEach(k -> keys.add(k));
}
}
for (KeyWrapper key : getKeys(realm, KeyUse.ENC, Algorithm.AES)) {
SecretKeyMetadata m = new SecretKeyMetadata();
m.setKid(key.getKid());
m.setProviderId(key.getProviderId());
m.setProviderPriority(key.getProviderPriority());
m.setStatus(key.getStatus());
keys.add(m);
}
return keys;
}
private boolean matches(KeyWrapper key, KeyUse use, String algorithm) {
return use.equals(key.getUse()) && key.getAlgorithms().contains(algorithm);
}
private List<KeyProvider> getProviders(RealmModel realm) {
List<KeyProvider> providers = providersMap.get(realm.getId());
if (providers == null) {
@ -255,10 +222,6 @@ public class DefaultKeyManager implements KeyManager {
List<ComponentModel> components = new LinkedList<>(realm.getComponents(realm.getId(), KeyProvider.class.getName()));
components.sort(new ProviderComparator());
boolean activeRsa = false;
boolean activeHmac = false;
boolean activeAes = false;
for (ComponentModel c : components) {
try {
ProviderFactory<KeyProvider> f = session.getKeycloakSessionFactory().getProviderFactory(KeyProvider.class, c.getProviderId());
@ -266,41 +229,30 @@ public class DefaultKeyManager implements KeyManager {
KeyProvider provider = factory.create(session, c);
session.enlistForClose(provider);
providers.add(provider);
if (provider.getType().equals(AlgorithmType.RSA)) {
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;
}
} else if (provider.getType().equals(AlgorithmType.AES)) {
AesKeyProvider r = (AesKeyProvider) provider;
if (r.getKid() != null && r.getSecretKey() != null) {
activeAes = true;
}
}
} catch (Throwable t) {
logger.errorv(t, "Failed to load provider {0}", c.getId());
}
}
if (!activeRsa) {
providersMap.put(realm.getId(), providers);
try {
getActiveKey(realm, KeyUse.SIG, Algorithm.RS256);
} catch (RuntimeException e) {
providers.add(new FailsafeRsaKeyProvider());
}
if (!activeHmac) {
try {
getActiveKey(realm, KeyUse.SIG, Algorithm.HS256);
} catch (RuntimeException e) {
providers.add(new FailsafeHmacKeyProvider());
}
if (!activeAes) {
try {
getActiveKey(realm, KeyUse.ENC, Algorithm.AES);
} catch (RuntimeException e) {
providers.add(new FailsafeAesKeyProvider());
}
providersMap.put(realm.getId(), providers);
}
return providers;
}

View file

@ -18,14 +18,32 @@
package org.keycloak.keys;
import org.jboss.logging.Logger;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyType;
import org.keycloak.crypto.KeyUse;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class FailsafeAesKeyProvider extends FailsafeSecretKeyProvider implements AesKeyProvider {
public class FailsafeAesKeyProvider extends FailsafeSecretKeyProvider {
private static final Logger logger = Logger.getLogger(FailsafeAesKeyProvider.class);
@Override
protected KeyUse getUse() {
return KeyUse.ENC;
}
@Override
protected String getType() {
return KeyType.OCT;
}
@Override
protected String getAlgorithm() {
return Algorithm.AES;
}
@Override
protected Logger logger() {
return logger;

View file

@ -20,6 +20,10 @@ package org.keycloak.keys;
import org.jboss.logging.Logger;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.Time;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyType;
import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.models.utils.KeycloakModelUtils;
import javax.crypto.SecretKey;
@ -29,12 +33,28 @@ import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class FailsafeHmacKeyProvider extends FailsafeSecretKeyProvider implements HmacKeyProvider {
public class FailsafeHmacKeyProvider extends FailsafeSecretKeyProvider {
private static final Logger logger = Logger.getLogger(FailsafeHmacKeyProvider.class);
@Override
protected KeyUse getUse() {
return KeyUse.SIG;
}
@Override
protected String getType() {
return KeyType.OCT;
}
@Override
protected String getAlgorithm() {
return Algorithm.HS256;
}
@Override
protected Logger logger() {
return logger;
}
}

View file

@ -20,77 +20,62 @@ package org.keycloak.keys;
import org.jboss.logging.Logger;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.Time;
import org.keycloak.crypto.*;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class FailsafeRsaKeyProvider implements RsaKeyProvider {
public class FailsafeRsaKeyProvider implements KeyProvider {
private static final Logger logger = Logger.getLogger(FailsafeRsaKeyProvider.class);
private static String KID;
private static KeyPair KEY_PAIR;
private static KeyWrapper KEY;
private static long EXPIRES;
private KeyPair keyPair;
private String kid;
private KeyWrapper key;
public FailsafeRsaKeyProvider() {
logger.errorv("No active keys found, using failsafe provider, please login to admin console to add keys. Clustering is not supported.");
synchronized (FailsafeRsaKeyProvider.class) {
if (EXPIRES < Time.currentTime()) {
KEY_PAIR = KeyUtils.generateRsaKeyPair(2048);
KID = KeyUtils.createKeyId(KEY_PAIR.getPublic());
KEY = createKeyWrapper();
EXPIRES = Time.currentTime() + 60 * 10;
if (EXPIRES > 0) {
logger.warnv("Keys expired, re-generated kid={0}", KID);
logger.warnv("Keys expired, re-generated kid={0}", KEY.getKid());
}
}
kid = KID;
keyPair = KEY_PAIR;
key = KEY;
}
}
@Override
public String getKid() {
return kid;
public List<KeyWrapper> getKeys() {
return Collections.singletonList(key);
}
@Override
public PrivateKey getPrivateKey() {
return keyPair.getPrivate();
private KeyWrapper createKeyWrapper() {
KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
KeyWrapper key = new KeyWrapper();
key.setKid(KeyUtils.createKeyId(keyPair.getPublic()));
key.setUse(KeyUse.SIG);
key.setType(KeyType.RSA);
key.setAlgorithms(Algorithm.RS256, Algorithm.RS384, Algorithm.RS512);
key.setStatus(KeyStatus.ACTIVE);
key.setSignKey(keyPair.getPrivate());
key.setVerifyKey(keyPair.getPublic());
return key;
}
@Override
public PublicKey getPublicKey(String kid) {
return kid.equals(this.kid) ? keyPair.getPublic() : null;
}
@Override
public X509Certificate getCertificate(String kid) {
return null;
}
@Override
public List<RsaKeyMetadata> getKeyMetadata() {
return Collections.emptyList();
}
@Override
public void close() {
}
}

View file

@ -17,74 +17,72 @@
package org.keycloak.keys;
import java.util.Collections;
import java.util.List;
import javax.crypto.SecretKey;
import org.jboss.logging.Logger;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.Time;
import org.keycloak.crypto.JavaAlgorithm;
import org.keycloak.crypto.KeyStatus;
import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
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 abstract class FailsafeSecretKeyProvider implements SecretKeyProvider {
public abstract class FailsafeSecretKeyProvider implements KeyProvider {
private static String KID;
private static SecretKey KEY;
private static KeyWrapper KEY;
private static long EXPIRES;
private SecretKey key;
private String kid;
private KeyWrapper key;
public FailsafeSecretKeyProvider() {
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), getJavaAlgorithmName());
KID = KeycloakModelUtils.generateId();
KEY = createKeyWrapper();
EXPIRES = Time.currentTime() + 60 * 10;
if (EXPIRES > 0) {
logger().warnv("Keys expired, re-generated kid={0}", KID);
logger().warnv("Keys expired, re-generated kid={0}", KEY.getKid());
}
}
kid = KID;
key = KEY;
}
}
@Override
public String getKid() {
return kid;
public List<KeyWrapper> getKeys() {
return Collections.singletonList(key);
}
@Override
public SecretKey getSecretKey() {
private KeyWrapper createKeyWrapper() {
SecretKey secretKey = KeyUtils.loadSecretKey(KeycloakModelUtils.generateSecret(32), JavaAlgorithm.getJavaAlgorithm(getAlgorithm()));
KeyWrapper key = new KeyWrapper();
key.setKid(KeycloakModelUtils.generateId());
key.setUse(getUse());
key.setType(getType());
key.setAlgorithms(getAlgorithm());
key.setStatus(KeyStatus.ACTIVE);
key.setSecretKey(secretKey);
return key;
}
@Override
public SecretKey getSecretKey(String kid) {
return kid.equals(this.kid) ? key : null;
}
protected abstract KeyUse getUse();
@Override
public List<SecretKeyMetadata> getKeyMetadata() {
return Collections.emptyList();
}
protected abstract String getType();
@Override
public void close() {
}
protected abstract String getAlgorithm();
protected abstract Logger logger();
}

View file

@ -18,14 +18,17 @@
package org.keycloak.keys;
import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyType;
import org.keycloak.crypto.KeyUse;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class GeneratedAesKeyProvider extends GeneratedSecretKeyProvider implements AesKeyProvider {
public class GeneratedAesKeyProvider extends GeneratedSecretKeyProvider implements KeyProvider {
public GeneratedAesKeyProvider(ComponentModel model) {
super(model);
super(model, KeyUse.ENC, KeyType.OCT, Algorithm.AES);
}
}

View file

@ -21,6 +21,7 @@ import java.util.List;
import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.KeyUse;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.ProviderConfigProperty;
@ -29,7 +30,7 @@ import static org.keycloak.provider.ProviderConfigProperty.LIST_TYPE;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class GeneratedAesKeyProviderFactory extends GeneratedSecretKeyProviderFactory<AesKeyProvider> implements AesKeyProviderFactory {
public class GeneratedAesKeyProviderFactory extends GeneratedSecretKeyProviderFactory<GeneratedSecretKeyProvider> {
private static final Logger logger = Logger.getLogger(GeneratedAesKeyProviderFactory.class);
@ -52,7 +53,7 @@ public class GeneratedAesKeyProviderFactory extends GeneratedSecretKeyProviderFa
.build();
@Override
public AesKeyProvider create(KeycloakSession session, ComponentModel model) {
public GeneratedAesKeyProvider create(KeycloakSession session, ComponentModel model) {
return new GeneratedAesKeyProvider(model);
}

View file

@ -18,15 +18,18 @@
package org.keycloak.keys;
import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyType;
import org.keycloak.crypto.KeyUse;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class GeneratedHmacKeyProvider extends GeneratedSecretKeyProvider implements HmacKeyProvider {
public class GeneratedHmacKeyProvider extends GeneratedSecretKeyProvider {
public GeneratedHmacKeyProvider(ComponentModel model) {
super(model);
super(model, KeyUse.SIG, KeyType.OCT, Algorithm.HS256);
}
}

View file

@ -34,7 +34,7 @@ import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class GeneratedHmacKeyProviderFactory extends GeneratedSecretKeyProviderFactory<HmacKeyProvider> implements HmacKeyProviderFactory {
public class GeneratedHmacKeyProviderFactory extends GeneratedSecretKeyProviderFactory<GeneratedHmacKeyProvider> {
private static final Logger logger = Logger.getLogger(GeneratedHmacKeyProviderFactory.class);
@ -49,7 +49,7 @@ public class GeneratedHmacKeyProviderFactory extends GeneratedSecretKeyProviderF
.build();
@Override
public HmacKeyProvider create(KeycloakSession session, ComponentModel model) {
public GeneratedHmacKeyProvider create(KeycloakSession session, ComponentModel model) {
return new GeneratedHmacKeyProvider(model);
}

View file

@ -110,18 +110,6 @@ public class GeneratedRsaKeyProviderFactory extends AbstractRsaKeyProviderFactor
return CONFIG_PROPERTIES;
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return ID;

View file

@ -17,86 +17,67 @@
package org.keycloak.keys;
import java.util.Collections;
import java.util.List;
import javax.crypto.SecretKey;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.JavaAlgorithm;
import org.keycloak.crypto.KeyStatus;
import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
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 abstract class GeneratedSecretKeyProvider implements SecretKeyProvider {
private final boolean enabled;
private final boolean active;
public abstract class GeneratedSecretKeyProvider implements KeyProvider {
private final KeyStatus status;
private final ComponentModel model;
private final String kid;
private final SecretKey secretKey;
private final KeyUse use;
private String type;
private final String algorithm;
public GeneratedSecretKeyProvider(ComponentModel model) {
this.enabled = model.get(Attributes.ENABLED_KEY, true);
this.active = model.get(Attributes.ACTIVE_KEY, true);
public GeneratedSecretKeyProvider(ComponentModel model, KeyUse use, String type, String algorithm) {
this.status = KeyStatus.from(model.get(Attributes.ACTIVE_KEY, true), model.get(Attributes.ENABLED_KEY, true));
this.kid = model.get(Attributes.KID_KEY);
this.model = model;
this.use = use;
this.type = type;
this.algorithm = algorithm;
if (model.hasNote(SecretKey.class.getName())) {
secretKey = model.getNote(SecretKey.class.getName());
} else {
secretKey = KeyUtils.loadSecretKey(Base64Url.decode(model.get(Attributes.SECRET_KEY)), getJavaAlgorithmName());
secretKey = KeyUtils.loadSecretKey(Base64Url.decode(model.get(Attributes.SECRET_KEY)), JavaAlgorithm.getJavaAlgorithm(algorithm));
model.setNote(SecretKey.class.getName(), secretKey);
}
}
@Override
public SecretKey getSecretKey() {
return isActive() ? secretKey : null;
}
public List<KeyWrapper> getKeys() {
KeyWrapper key = new KeyWrapper();
@Override
public SecretKey getSecretKey(String kid) {
return isEnabled() && kid.equals(this.kid) ? secretKey : null;
}
key.setProviderId(model.getId());
key.setProviderPriority(model.get("priority", 0l));
@Override
public String getKid() {
return isActive() ? kid : null;
}
key.setKid(kid);
key.setUse(use);
key.setType(type);
key.setAlgorithms(algorithm);
key.setStatus(status);
key.setSecretKey(secretKey);
@Override
public List<SecretKeyMetadata> getKeyMetadata() {
if (kid != null && secretKey != null) {
SecretKeyMetadata k = new SecretKeyMetadata();
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();
}
return Collections.singletonList(key);
}
@Override
public void close() {
}
private boolean isEnabled() {
return secretKey != null && enabled;
}
private boolean isActive() {
return isEnabled() && active;
}
}

View file

@ -66,17 +66,5 @@ public abstract class GeneratedSecretKeyProviderFactory<T extends KeyProvider> i
protected abstract Logger logger();
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
protected abstract int getDefaultKeySize();
}

View file

@ -20,6 +20,7 @@ package org.keycloak.keys;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.PemUtils;
import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.models.RealmModel;
import java.security.KeyPair;
@ -38,7 +39,7 @@ public class ImportedRsaKeyProvider extends AbstractRsaKeyProvider {
}
@Override
public Keys loadKeys(RealmModel realm, ComponentModel model) {
public KeyWrapper loadKey(RealmModel realm, ComponentModel model) {
String privateRsaKeyPem = model.getConfig().getFirst(Attributes.PRIVATE_KEY_KEY);
String certificatePem = model.getConfig().getFirst(Attributes.CERTIFICATE_KEY);
@ -48,9 +49,7 @@ public class ImportedRsaKeyProvider extends AbstractRsaKeyProvider {
KeyPair keyPair = new KeyPair(publicKey, privateKey);
X509Certificate certificate = PemUtils.decodeCertificate(certificatePem);
String kid = KeyUtils.createKeyId(keyPair.getPublic());
return new Keys(kid, keyPair, certificate);
return createKeyWrapper(keyPair, certificate);
}
}

View file

@ -17,14 +17,12 @@
package org.keycloak.keys;
import org.keycloak.Config;
import org.keycloak.common.util.CertificateUtils;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.PemUtils;
import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ConfigurationValidationHelper;
import org.keycloak.provider.ProviderConfigProperty;
@ -106,18 +104,6 @@ public class ImportedRsaKeyProviderFactory extends AbstractRsaKeyProviderFactory
return CONFIG_PROPERTIES;
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return ID;

View file

@ -17,23 +17,16 @@
package org.keycloak.keys;
import org.jboss.logging.Logger;
import org.keycloak.common.util.CertificateUtils;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.models.RealmModel;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
@ -42,14 +35,12 @@ import java.security.cert.X509Certificate;
*/
public class JavaKeystoreKeyProvider extends AbstractRsaKeyProvider {
private static final Logger logger = Logger.getLogger(JavaKeystoreKeyProvider.class);
public JavaKeystoreKeyProvider(RealmModel realm, ComponentModel model) {
super(realm, model);
}
@Override
protected Keys loadKeys(RealmModel realm, ComponentModel model) {
protected KeyWrapper loadKey(RealmModel realm, ComponentModel model) {
try {
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream(model.get(JavaKeystoreKeyProviderFactory.KEYSTORE_KEY)), model.get(JavaKeystoreKeyProviderFactory.KEYSTORE_PASSWORD_KEY).toCharArray());
@ -64,9 +55,7 @@ public class JavaKeystoreKeyProvider extends AbstractRsaKeyProvider {
certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName());
}
String kid = KeyUtils.createKeyId(keyPair.getPublic());
return new Keys(kid, keyPair, certificate);
return createKeyWrapper(keyPair, certificate);
} catch (KeyStoreException kse) {
throw new RuntimeException("KeyStore error on server. " + kse.getMessage(), kse);
} catch (FileNotFoundException fnfe) {

View file

@ -77,7 +77,7 @@ public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactor
try {
new JavaKeystoreKeyProvider(session.getContext().getRealm(), model)
.loadKeys(session.getContext().getRealm(), model);
.loadKey(session.getContext().getRealm(), model);
} catch (Throwable t) {
logger.error("Failed to load keys.", t);
throw new ComponentValidationException("Failed to load keys. " + t.getMessage(), t);
@ -94,18 +94,6 @@ public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactor
return CONFIG_PROPERTIES;
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return ID;

View file

@ -26,7 +26,6 @@ import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jwk.JWKBuilder;
import org.keycloak.keys.KeyMetadata;
import org.keycloak.keys.RsaKeyMetadata;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
@ -195,7 +194,7 @@ public class OIDCLoginProtocolService {
@Produces(MediaType.APPLICATION_JSON)
@NoCache
public Response certs() {
List<RsaKeyMetadata> publicKeys = session.keys().getRsaKeys(realm, false);
List<RsaKeyMetadata> publicKeys = session.keys().getRsaKeys(realm);
JWK[] keys = new JWK[publicKeys.size()];
int i = 0;

View file

@ -23,6 +23,7 @@ import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.common.VerificationException;
import org.keycloak.common.util.PemUtils;
import org.keycloak.common.util.StreamUtil;
import org.keycloak.crypto.KeyStatus;
import org.keycloak.dom.saml.v2.SAML2Object;
import org.keycloak.dom.saml.v2.assertion.BaseIDAbstractType;
import org.keycloak.dom.saml.v2.assertion.NameIDType;
@ -80,7 +81,6 @@ import java.util.Set;
import java.util.TreeSet;
import org.keycloak.common.util.StringPropertyReplacer;
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
import org.keycloak.keys.KeyMetadata;
import org.keycloak.rotation.HardcodedKeyLocator;
import org.keycloak.rotation.KeyLocator;
import org.keycloak.saml.SPMetadataDescriptor;
@ -591,8 +591,8 @@ public class SamlService extends AuthorizationEndpointBase {
StringBuilder keysString = new StringBuilder();
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())
: (o1.getStatus() == KeyMetadata.Status.PASSIVE ? 1 : -1));
keys.addAll(session.keys().getRsaKeys(realm, false));
: (o1.getStatus() == KeyStatus.PASSIVE ? 1 : -1));
keys.addAll(session.keys().getRsaKeys(realm));
for (RsaKeyMetadata key : keys) {
addKeyInfo(keysString, key, KeyTypes.SIGNING.value());
}

View file

@ -19,6 +19,8 @@ package org.keycloak.protocol.saml.installation;
import org.keycloak.Config;
import org.keycloak.common.util.PemUtils;
import org.keycloak.crypto.KeyStatus;
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
import org.keycloak.keys.RsaKeyMetadata;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
@ -27,6 +29,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.protocol.ClientInstallationProvider;
import org.keycloak.protocol.saml.SamlClient;
import org.keycloak.protocol.saml.SamlProtocol;
import org.keycloak.saml.SPMetadataDescriptor;
import org.keycloak.services.resources.RealmsResource;
import javax.ws.rs.core.MediaType;
@ -35,9 +38,6 @@ import javax.ws.rs.core.UriBuilder;
import java.net.URI;
import java.util.Set;
import java.util.TreeSet;
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
import org.keycloak.keys.KeyMetadata;
import org.keycloak.saml.SPMetadataDescriptor;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -90,8 +90,8 @@ public class SamlIDPDescriptorClientInstallation implements ClientInstallationPr
// keys
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())
: (o1.getStatus() == KeyMetadata.Status.PASSIVE ? 1 : -1));
keys.addAll(session.keys().getRsaKeys(realm, false));
: (o1.getStatus() == KeyStatus.PASSIVE ? 1 : -1));
keys.addAll(session.keys().getRsaKeys(realm));
for (RsaKeyMetadata key : keys) {
addKeyInfo(sb, key, KeyTypes.SIGNING.value());
}

View file

@ -19,9 +19,9 @@ package org.keycloak.services.resources.admin;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.common.util.PemUtils;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.jose.jws.AlgorithmType;
import org.keycloak.keys.SecretKeyMetadata;
import org.keycloak.keys.RsaKeyMetadata;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeyManager;
import org.keycloak.models.RealmModel;
@ -58,48 +58,30 @@ public class KeyResource {
public KeysMetadataRepresentation getKeyMetadata() {
auth.realm().requireViewRealm();
KeyManager keystore = session.keys();
KeysMetadataRepresentation keys = new KeysMetadataRepresentation();
keys.setKeys(new LinkedList<>());
keys.setActive(new HashMap<>());
Map<String, String> active = new HashMap<>();
active.put(AlgorithmType.RSA.name(), keystore.getActiveRsaKey(realm).getKid());
active.put(AlgorithmType.HMAC.name(), keystore.getActiveHmacKey(realm).getKid());
active.put(AlgorithmType.AES.name(), keystore.getActiveAesKey(realm).getKid());
keys.setActive(active);
for (KeyWrapper key : session.keys().getKeys(realm)) {
KeysMetadataRepresentation.KeyMetadataRepresentation r = new KeysMetadataRepresentation.KeyMetadataRepresentation();
r.setProviderId(key.getProviderId());
r.setProviderPriority(key.getProviderPriority());
r.setKid(key.getKid());
r.setStatus(key.getStatus() != null ? key.getStatus().name() : null);
r.setType(key.getType());
r.setAlgorithms(key.getAlgorithms());
r.setPublicKey(key.getVerifyKey() != null ? PemUtils.encodeKey(key.getVerifyKey()) : null);
r.setCertificate(key.getCertificate() != null ? PemUtils.encodeCertificate(key.getCertificate()) : null);
keys.getKeys().add(r);
List<KeysMetadataRepresentation.KeyMetadataRepresentation> l = new LinkedList<>();
for (RsaKeyMetadata m : session.keys().getRsaKeys(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.RSA.name());
r.setPublicKey(PemUtils.encodeKey(m.getPublicKey()));
r.setCertificate(PemUtils.encodeCertificate(m.getCertificate()));
l.add(r);
if (key.getStatus().isActive()) {
for (String a : key.getAlgorithms()) {
if (!keys.getActive().containsKey(a)) {
keys.getActive().put(a, key.getKid());
}
}
for (SecretKeyMetadata 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);
}
for (SecretKeyMetadata m : session.keys().getAesKeys(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.AES.name());
l.add(r);
}
keys.setKeys(l);
return keys;
}

View file

@ -23,6 +23,7 @@ import org.keycloak.admin.client.resource.ClientScopeResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RoleResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.crypto.Algorithm;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ClientScopeRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
@ -255,7 +256,7 @@ public class ApiUtil {
public static KeysMetadataRepresentation.KeyMetadataRepresentation findActiveKey(RealmResource realm) {
KeysMetadataRepresentation keyMetadata = realm.keys().getKeyMetadata();
String activeKid = keyMetadata.getActive().get("RSA");
String activeKid = keyMetadata.getActive().get(Algorithm.RS256);
for (KeysMetadataRepresentation.KeyMetadataRepresentation rep : keyMetadata.getKeys()) {
if (rep.getKid().equals(activeKid)) {
return rep;

View file

@ -1,6 +1,7 @@
package org.keycloak.testsuite.util;
import org.keycloak.common.util.BouncyIntegration;
import org.keycloak.representations.idm.KeysMetadataRepresentation;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
@ -39,4 +40,15 @@ public class KeyUtils {
throw new RuntimeException(e);
}
}
public static KeysMetadataRepresentation.KeyMetadataRepresentation getActiveKey(KeysMetadataRepresentation keys, String algorithm) {
String kid = keys.getActive().get(algorithm);
for (KeysMetadataRepresentation.KeyMetadataRepresentation k : keys.getKeys()) {
if (k.getKid().equals(kid)) {
return k;
}
}
throw new RuntimeException("Active key not found");
}
}

View file

@ -38,6 +38,7 @@ import org.keycloak.common.VerificationException;
import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.common.util.PemUtils;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.crypto.Algorithm;
import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
@ -1099,7 +1100,7 @@ public class OAuthClient {
public PublicKey getRealmPublicKey(String realm) {
if (!publicKeys.containsKey(realm)) {
KeysMetadataRepresentation keyMetadata = adminClient.realms().realm(realm).keys().getKeyMetadata();
String activeKid = keyMetadata.getActive().get("RSA");
String activeKid = keyMetadata.getActive().get(Algorithm.RS256);
PublicKey publicKey = null;
for (KeysMetadataRepresentation.KeyMetadataRepresentation rep : keyMetadata.getKeys()) {
if (rep.getKid().equals(activeKid)) {

View file

@ -42,7 +42,7 @@ import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.StreamUtil;
import org.keycloak.common.util.Time;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.jose.jws.AlgorithmType;
import org.keycloak.crypto.Algorithm;
import org.keycloak.keys.KeyProvider;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
@ -308,7 +308,7 @@ public class OIDCPublicKeyRotationAdapterTest extends AbstractServletsAdapterTes
private String getActiveKeyProvider() {
KeysMetadataRepresentation keyMetadata = adminClient.realm(DEMO).keys().getKeyMetadata();
String activeKid = keyMetadata.getActive().get(AlgorithmType.RSA.name());
String activeKid = keyMetadata.getActive().get(Algorithm.RS256);
for (KeysMetadataRepresentation.KeyMetadataRepresentation rep : keyMetadata.getKeys()) {
if (rep.getKid().equals(activeKid)) {
return rep.getProviderId();

View file

@ -28,6 +28,7 @@ import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.common.util.*;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.crypto.Algorithm;
import org.keycloak.keys.KeyProvider;
import org.keycloak.keys.PublicKeyStorageUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
@ -293,7 +294,7 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
private void rotateKeys() {
String activeKid = providerRealm().keys().getKeyMetadata().getActive().get("RSA");
String activeKid = providerRealm().keys().getKeyMetadata().getActive().get(Algorithm.RS256);
// Rotate public keys on the parent broker
String realmId = providerRealm().toRepresentation().getId();
@ -308,7 +309,7 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
assertEquals(201, response.getStatus());
response.close();
String updatedActiveKid = providerRealm().keys().getKeyMetadata().getActive().get("RSA");
String updatedActiveKid = providerRealm().keys().getKeyMetadata().getActive().get(Algorithm.RS256);
assertNotEquals(activeKid, updatedActiveKid);
}

View file

@ -2,10 +2,12 @@ package org.keycloak.testsuite.broker;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.broker.saml.SAMLIdentityProviderConfig;
import org.keycloak.crypto.Algorithm;
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
import org.keycloak.protocol.saml.SamlConfigAttributes;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.KeysMetadataRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.util.DocumentUtil;
@ -16,6 +18,7 @@ import org.keycloak.testsuite.arquillian.SuiteContext;
import org.keycloak.testsuite.saml.AbstractSamlTest;
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
import org.keycloak.testsuite.updaters.IdentityProviderAttributeUpdater;
import org.keycloak.testsuite.util.KeyUtils;
import org.keycloak.testsuite.util.SamlClient;
import org.keycloak.testsuite.util.SamlClient.Binding;
import org.keycloak.testsuite.util.SamlClientBuilder;
@ -66,7 +69,7 @@ public class KcSamlSignedBrokerTest extends KcSamlBrokerTest {
public List<ClientRepresentation> createProviderClients(SuiteContext suiteContext) {
List<ClientRepresentation> clientRepresentationList = super.createProviderClients(suiteContext);
String consumerCert = adminClient.realm(consumerRealmName()).keys().getKeyMetadata().getKeys().get(0).getCertificate();
String consumerCert = KeyUtils.getActiveKey(adminClient.realm(consumerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
Assert.assertThat(consumerCert, Matchers.notNullValue());
for (ClientRepresentation client : clientRepresentationList) {
@ -93,7 +96,7 @@ public class KcSamlSignedBrokerTest extends KcSamlBrokerTest {
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) {
IdentityProviderRepresentation result = super.setUpIdentityProvider(suiteContext);
String providerCert = adminClient.realm(providerRealmName()).keys().getKeyMetadata().getKeys().get(0).getCertificate();
String providerCert = KeyUtils.getActiveKey(adminClient.realm(providerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
Assert.assertThat(providerCert, Matchers.notNullValue());
Map<String, String> config = result.getConfig();
@ -121,10 +124,10 @@ public class KcSamlSignedBrokerTest extends KcSamlBrokerTest {
final ClientResource clientResource = realmsResouce().realm(bc.providerRealmName()).clients().get(client.getId());
Assert.assertThat(clientResource, Matchers.notNullValue());
String providerCert = adminClient.realm(bc.providerRealmName()).keys().getKeyMetadata().getKeys().get(0).getCertificate();
String providerCert = KeyUtils.getActiveKey(adminClient.realm(bc.providerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
Assert.assertThat(providerCert, Matchers.notNullValue());
String consumerCert = adminClient.realm(bc.consumerRealmName()).keys().getKeyMetadata().getKeys().get(0).getCertificate();
String consumerCert = KeyUtils.getActiveKey(adminClient.realm(bc.consumerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
Assert.assertThat(consumerCert, Matchers.notNullValue());
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)

View file

@ -24,6 +24,8 @@ import org.junit.Rule;
import org.junit.Test;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyType;
import org.keycloak.jose.jws.AlgorithmType;
import org.keycloak.keys.GeneratedHmacKeyProviderFactory;
import org.keycloak.keys.KeyProvider;
@ -90,14 +92,14 @@ public class GeneratedHmacKeyProviderTest extends AbstractKeycloakTest {
KeysMetadataRepresentation.KeyMetadataRepresentation key = null;
for (KeysMetadataRepresentation.KeyMetadataRepresentation k : keys.getKeys()) {
if (k.getType().equals(AlgorithmType.HMAC.name())) {
if (k.getAlgorithms().contains(Algorithm.HS256)) {
key = k;
break;
}
}
assertEquals(id, key.getProviderId());
assertEquals(AlgorithmType.HMAC.name(), key.getType());
assertEquals(KeyType.OCT, key.getType());
assertEquals(priority, key.getProviderPriority());
ComponentRepresentation component = testingClient.server("test").fetch(RunHelpers.internalComponent(id));
@ -125,14 +127,14 @@ public class GeneratedHmacKeyProviderTest extends AbstractKeycloakTest {
KeysMetadataRepresentation.KeyMetadataRepresentation key = null;
for (KeysMetadataRepresentation.KeyMetadataRepresentation k : keys.getKeys()) {
if (k.getType().equals(AlgorithmType.HMAC.name())) {
if (k.getAlgorithms().contains(Algorithm.HS256)) {
key = k;
break;
}
}
assertEquals(id, key.getProviderId());
assertEquals(AlgorithmType.HMAC.name(), key.getType());
assertEquals(KeyType.OCT, key.getType());
assertEquals(priority, key.getProviderPriority());
ComponentRepresentation component = testingClient.server("test").fetch(RunHelpers.internalComponent(id));

View file

@ -24,6 +24,7 @@ import org.keycloak.common.util.CertificateUtils;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.PemUtils;
import org.keycloak.crypto.Algorithm;
import org.keycloak.jose.jws.AlgorithmType;
import org.keycloak.keys.Attributes;
import org.keycloak.keys.ImportedRsaKeyProviderFactory;
@ -90,7 +91,7 @@ public class ImportedRsaKeyProviderTest extends AbstractKeycloakTest {
KeysMetadataRepresentation keys = adminClient.realm("test").keys().getKeyMetadata();
assertEquals(kid, keys.getActive().get(AlgorithmType.RSA.name()));
assertEquals(kid, keys.getActive().get(Algorithm.RS256));
KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0);

View file

@ -30,6 +30,7 @@ import org.keycloak.common.VerificationException;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.PemUtils;
import org.keycloak.crypto.Algorithm;
import org.keycloak.keys.Attributes;
import org.keycloak.keys.GeneratedHmacKeyProviderFactory;
import org.keycloak.keys.KeyProvider;
@ -218,7 +219,7 @@ public class KeyRotationTest extends AbstractKeycloakTest {
PublicKey keys2 = createKeys2();
KeysMetadataRepresentation keyMetadata = adminClient.realm("test").keys().getKeyMetadata();
assertEquals(PemUtils.encodeKey(keys2), keyMetadata.getKeys().get(0).getPublicKey());
assertEquals(PemUtils.encodeKey(keys2), org.keycloak.testsuite.util.KeyUtils.getActiveKey(keyMetadata, Algorithm.RS256).getPublicKey());
dropKeys1();
dropKeys2();
@ -227,7 +228,7 @@ public class KeyRotationTest extends AbstractKeycloakTest {
@Test
public void rotateKeys() throws InterruptedException {
for (int i = 0; i < 10; i++) {
String activeKid = adminClient.realm("test").keys().getKeyMetadata().getActive().get("RSA");
String activeKid = adminClient.realm("test").keys().getKeyMetadata().getActive().get(Algorithm.RS256);
// Rotate public keys on the parent broker
String realmId = adminClient.realm("test").toRepresentation().getId();
@ -244,7 +245,7 @@ public class KeyRotationTest extends AbstractKeycloakTest {
getCleanup().addComponentId(newId);
response.close();
String updatedActiveKid = adminClient.realm("test").keys().getKeyMetadata().getActive().get("RSA");
String updatedActiveKid = adminClient.realm("test").keys().getKeyMetadata().getActive().get(Algorithm.RS256);
assertNotEquals(activeKid, updatedActiveKid);
}
}

View file

@ -16,8 +16,6 @@
*/
package org.keycloak.testsuite;
import org.jboss.logging.Logger;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
@ -29,8 +27,6 @@ import java.net.URI;
*/
public class ApiUtil {
private static final Logger log = Logger.getLogger(ApiUtil.class);
public static String getCreatedId(Response response) {
URI location = response.getLocation();
if (!response.getStatusInfo().equals(Status.CREATED)) {

View file

@ -1388,7 +1388,6 @@ authz-evaluation-authorization-data.tooltip=Represents a token carrying authoriz
authz-show-authorization-data=Show Authorization Data
keys=Keys
all=All
status=Status
keystore=Keystore
keystores=Keystores
@ -1396,6 +1395,10 @@ add-keystore=Add Keystore
add-keystore.placeholder=Add keystore...
view=View
active=Active
passive=Passive
disabled=Disabled
algorithms=Algorithms
providerHelpText=Provider description
Sunday=Sunday
Monday=Monday

View file

@ -289,8 +289,23 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'RealmKeysCtrl'
})
.when('/realms/:realm/keys/list', {
templateUrl : resourceUrl + '/partials/realm-keys-list.html',
.when('/realms/:realm/keys/passive', {
templateUrl : resourceUrl + '/partials/realm-keys-passive.html',
resolve : {
realm : function(RealmLoader) {
return RealmLoader();
},
serverInfo : function(ServerInfoLoader) {
return ServerInfoLoader();
},
keys: function(RealmKeysLoader) {
return RealmKeysLoader();
}
},
controller : 'RealmKeysCtrl'
})
.when('/realms/:realm/keys/disabled', {
templateUrl : resourceUrl + '/partials/realm-keys-disabled.html',
resolve : {
realm : function(RealmLoader) {
return RealmLoader();

View file

@ -0,0 +1,71 @@
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<kc-tabs-realm></kc-tabs-realm>
<ul class="nav nav-tabs nav-tabs-pf">
<li><a href="#/realms/{{realm.realm}}/keys">{{:: 'active' | translate}}</a></li>
<li><a href="#/realms/{{realm.realm}}/keys/passive">{{:: 'passive' | translate}}</a></li>
<li class="active"><a href="#/realms/{{realm.realm}}/keys/disabled">{{:: 'disabled' | translate}}</a></li>
<li><a href="#/realms/{{realm.realm}}/keys/providers">{{:: 'providers' | translate}}</a></li>
</ul>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th class="kc-table-actions" colspan="7">
<div class="form-inline">
<div class="form-group">
<div class="input-group">
<input type="text" placeholder="{{:: 'search.placeholder' | translate}}" data-ng-model="search" class="form-control search"">
<div class="input-group-addon">
<i class="fa fa-search"></i>
</div>
</div>
</div>
</div>
</th>
</tr>
<tr>
<th>{{:: 'algorithms' | translate}}</th>
<th>{{:: 'type' | translate}}</th>
<th>{{:: 'kid' | translate}}</th>
<th>{{:: 'priority' | translate}}</th>
<th>{{:: 'provider' | translate}}</th>
<th colspan="2">{{:: 'publicKeys' | translate}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="key in keys | filter:search | filter:{status:'DISABLED'}">
<td>{{key.algorithm.sort().join(', ')}}</td>
<td>{{key.type}}</td>
<td>{{key.kid}}</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 data-ng-show="key.type === 'RSA'" class="kc-action-cell" data-ng-click="viewKey(key.publicKey)">{{:: 'publicKey' | translate}}</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.type !== 'RSA'" colspan="2"></td>
</tr>
</tbody>
</table>
</div>
<kc-menu></kc-menu>

View file

@ -20,15 +20,30 @@
<ul class="nav nav-tabs nav-tabs-pf">
<li><a href="#/realms/{{realm.realm}}/keys">{{:: 'active' | translate}}</a></li>
<li class="active"><a href="#/realms/{{realm.realm}}/keys/list">{{:: 'all' | translate}}</a></li>
<li class="active"><a href="#/realms/{{realm.realm}}/keys/passive">{{:: 'passive' | translate}}</a></li>
<li><a href="#/realms/{{realm.realm}}/keys/disabled">{{:: 'disabled' | translate}}</a></li>
<li><a href="#/realms/{{realm.realm}}/keys/providers">{{:: 'providers' | translate}}</a></li>
</ul>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th class="kc-table-actions" colspan="7">
<div class="form-inline">
<div class="form-group">
<div class="input-group">
<input type="text" placeholder="{{:: 'search.placeholder' | translate}}" data-ng-model="search" class="form-control search"">
<div class="input-group-addon">
<i class="fa fa-search"></i>
</div>
</div>
</div>
</div>
</th>
</tr>
<tr>
<th>{{:: 'algorithms' | translate}}</th>
<th>{{:: 'type' | translate}}</th>
<th>{{:: 'status' | translate}}</th>
<th>{{:: 'kid' | translate}}</th>
<th>{{:: 'priority' | translate}}</th>
<th>{{:: 'provider' | translate}}</th>
@ -36,9 +51,9 @@
</tr>
</thead>
<tbody>
<tr ng-repeat="key in keys">
<tr ng-repeat="key in keys | filter:search | filter:{status:'PASSIVE'}">
<td>{{key.algorithm.sort().join(', ')}}</td>
<td>{{key.type}}</td>
<td>{{key.status}}</td>
<td>{{key.kid}}</td>
<td>{{key.providerPriority}}</td>
<td><a href="#/realms/{{realm.realm}}/keys/providers/{{key.provider.providerId}}/{{key.provider.id}}">{{key.provider.name}}</a></td>

View file

@ -20,16 +20,26 @@
<ul class="nav nav-tabs nav-tabs-pf">
<li><a href="#/realms/{{realm.realm}}/keys">{{:: 'active' | translate}}</a></li>
<li><a href="#/realms/{{realm.realm}}/keys/list">{{:: 'all' | translate}}</a></li>
<li><a href="#/realms/{{realm.realm}}/keys/passive">{{:: 'passive' | translate}}</a></li>
<li><a href="#/realms/{{realm.realm}}/keys/disabled">{{:: 'disabled' | translate}}</a></li>
<li class="active"><a href="#/realms/{{realm.realm}}/keys/providers">{{:: 'providers' | translate}}</a></li>
</ul>
<table class="table table-striped table-bordered">
<thead>
<tr ng-show="providers.length > 0 && access.manageRealm">
<th colspan="7" class="kc-table-actions">
<div class="pull-right">
<div>
<tr>
<th class="kc-table-actions" colspan="7">
<div class="form-inline">
<div class="form-group">
<div class="input-group">
<input type="text" placeholder="{{:: 'search.placeholder' | translate}}" data-ng-model="search" class="form-control search">
<div class="input-group-addon">
<i class="fa fa-search"></i>
</div>
</div>
</div>
<div class="pull-right" data-ng-show="access.manageClients">
<select class="form-control" ng-model="selectedProvider"
ng-options="p.id for p in providers"
data-ng-change="addProvider(selectedProvider); selectedProvider = null">
@ -40,20 +50,18 @@
</th>
</tr>
<tr data-ng-show="instances && instances.length > 0">
<th>{{:: 'type' | translate}}</th>
<th>{{:: 'name' | translate}}</th>
<th>{{:: 'id' | translate}}</th>
<th>{{:: 'provider' | translate}}</th>
<th>{{:: 'providerHelpText' | translate}}</th>
<th>{{:: 'priority' | translate}}</th>
<th colspan="2">{{:: 'actions' | translate}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="instance in instances">
<td>{{instance.provider.metadata.algorithmType}}</td>
<td>{{instance.name}}</td>
<td><a href="#/realms/{{realm.realm}}/keys/providers/{{instance.providerId}}/{{instance.id}}">{{instance.id}}</a></td>
<tr ng-repeat="instance in instances | filter:search">
<td><a href="#/realms/{{realm.realm}}/keys/providers/{{instance.providerId}}/{{instance.id}}">{{instance.name}}</a></td>
<td>{{instance.providerId}}</td>
<td>{{instance.provider.helpText}}</td>
<td>{{instance.config['priority'][0]}}</td>
<td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/keys/providers/{{instance.providerId}}/{{instance.id}}">{{:: 'edit' | translate}}</td>
<td class="kc-action-cell" ng-show="instances.length > 1" data-ng-click="removeInstance(instance)">{{:: 'delete' | translate}}</td>

View file

@ -1,25 +1,61 @@
<!--
~ 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.
-->
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<kc-tabs-realm></kc-tabs-realm>
<ul class="nav nav-tabs nav-tabs-pf">
<li class="active"><a href="#/realms/{{realm.realm}}/keys">{{:: 'active' | translate}}</a></li>
<li><a href="#/realms/{{realm.realm}}/keys/list">{{:: 'all' | translate}}</a></li>
<li><a href="#/realms/{{realm.realm}}/keys/passive">{{:: 'passive' | translate}}</a></li>
<li><a href="#/realms/{{realm.realm}}/keys/disabled">{{:: 'disabled' | translate}}</a></li>
<li><a href="#/realms/{{realm.realm}}/keys/providers">{{:: 'providers' | translate}}</a></li>
</ul>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th class="kc-table-actions" colspan="7">
<div class="form-inline">
<div class="form-group">
<div class="input-group">
<input type="text" placeholder="{{:: 'search.placeholder' | translate}}" data-ng-model="search" class="form-control search"">
<div class="input-group-addon">
<i class="fa fa-search"></i>
</div>
</div>
</div>
</div>
</th>
</tr>
<tr>
<th>{{:: 'algorithms' | translate}}</th>
<th>{{:: 'type' | translate}}</th>
<th>{{:: 'kid' | translate}}</th>
<th>{{:: 'priority' | translate}}</th>
<th>{{:: 'provider' | translate}}</th>
<th colspan="2">{{:: 'publicKeys' | translate}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="key in active">
<tr ng-repeat="key in keys | filter:search | filter:{status:'ACTIVE'}">
<td>{{key.algorithm.sort().join(', ')}}</td>
<td>{{key.type}}</td>
<td>{{key.kid}}</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 data-ng-show="key.type === 'RSA'" class="kc-action-cell" data-ng-click="viewKey(key.publicKey)">{{:: 'publicKey' | translate}}</td>