KEYCLOAK-7701 Refactor key providers to support additional algorithms
This commit is contained in:
parent
a5d155a35a
commit
3c5027de3c
53 changed files with 832 additions and 815 deletions
27
server-spi-private/src/main/java/org/keycloak/keys/HmacKeyProvider.java → core/src/main/java/org/keycloak/crypto/Algorithm.java
Normal file → Executable file
27
server-spi-private/src/main/java/org/keycloak/keys/HmacKeyProvider.java → core/src/main/java/org/keycloak/crypto/Algorithm.java
Normal file → Executable file
|
@ -14,22 +14,19 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package org.keycloak.crypto;
|
||||||
|
|
||||||
package org.keycloak.keys;
|
public interface Algorithm {
|
||||||
|
|
||||||
import org.keycloak.jose.jws.AlgorithmType;
|
String HS256 = "HS256";
|
||||||
|
String HS384 = "HS384";
|
||||||
/**
|
String HS512 = "HS512";
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
String RS256 = "RS256";
|
||||||
*/
|
String RS384 = "RS384";
|
||||||
public interface HmacKeyProvider extends SecretKeyProvider {
|
String RS512 = "RS512";
|
||||||
|
String ES256 = "ES256";
|
||||||
default AlgorithmType getType() {
|
String ES384 = "ES384";
|
||||||
return AlgorithmType.HMAC;
|
String ES512 = "ES512";
|
||||||
}
|
|
||||||
|
|
||||||
default String getJavaAlgorithmName() {
|
|
||||||
return "HmacSHA256";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
String AES = "AES";
|
||||||
}
|
}
|
42
core/src/main/java/org/keycloak/crypto/JavaAlgorithm.java
Normal file
42
core/src/main/java/org/keycloak/crypto/JavaAlgorithm.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.
|
* and other contributors as indicated by the @author tags.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -14,21 +14,26 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package org.keycloak.crypto;
|
||||||
|
|
||||||
package org.keycloak.keys;
|
public enum KeyStatus {
|
||||||
|
|
||||||
import org.keycloak.jose.jws.AlgorithmType;
|
ACTIVE, PASSIVE, DISABLED;
|
||||||
|
|
||||||
/**
|
public static KeyStatus from(boolean active, boolean enabled) {
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
if (!enabled) {
|
||||||
*/
|
return KeyStatus.DISABLED;
|
||||||
public interface AesKeyProvider extends SecretKeyProvider {
|
} else {
|
||||||
|
return active ? KeyStatus.ACTIVE : KeyStatus.PASSIVE;
|
||||||
default AlgorithmType getType() {
|
}
|
||||||
return AlgorithmType.AES;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default String getJavaAlgorithmName() {
|
public boolean isActive() {
|
||||||
return "AES";
|
return this.equals(ACTIVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return this.equals(ACTIVE) || this.equals(PASSIVE);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -14,22 +14,12 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package org.keycloak.crypto;
|
||||||
|
|
||||||
package org.keycloak.keys;
|
public interface KeyType {
|
||||||
|
|
||||||
import org.keycloak.jose.jws.AlgorithmType;
|
String EC = "EC";
|
||||||
|
String RSA = "RSA";
|
||||||
import java.util.Collections;
|
String OCT = "OCT";
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -14,22 +14,11 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package org.keycloak.crypto;
|
||||||
|
|
||||||
package org.keycloak.keys;
|
public enum KeyUse {
|
||||||
|
|
||||||
import org.keycloak.jose.jws.AlgorithmType;
|
SIG,
|
||||||
|
ENC
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
135
core/src/main/java/org/keycloak/crypto/KeyWrapper.java
Normal file
135
core/src/main/java/org/keycloak/crypto/KeyWrapper.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ package org.keycloak.representations.idm;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -54,6 +55,7 @@ public class KeysMetadataRepresentation {
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
private String type;
|
private String type;
|
||||||
|
private Set<String> algorithms;
|
||||||
|
|
||||||
private String publicKey;
|
private String publicKey;
|
||||||
private String certificate;
|
private String certificate;
|
||||||
|
@ -98,6 +100,14 @@ public class KeysMetadataRepresentation {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set<String> getAlgorithms() {
|
||||||
|
return algorithms;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAlgorithms(Set<String> algorithms) {
|
||||||
|
this.algorithms = algorithms;
|
||||||
|
}
|
||||||
|
|
||||||
public String getPublicKey() {
|
public String getPublicKey() {
|
||||||
return publicKey;
|
return publicKey;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.keycloak.keys;
|
package org.keycloak.keys;
|
||||||
|
|
||||||
|
import org.keycloak.crypto.KeyWrapper;
|
||||||
import org.keycloak.jose.jws.AlgorithmType;
|
import org.keycloak.jose.jws.AlgorithmType;
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
|
|
||||||
|
@ -28,26 +29,15 @@ 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<T extends KeyMetadata> extends Provider {
|
public interface KeyProvider extends Provider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the algorithm type the keys can be used for
|
* Returns the key
|
||||||
*
|
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
AlgorithmType getType();
|
List<KeyWrapper> getKeys();
|
||||||
|
|
||||||
/**
|
default void close() {
|
||||||
* 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();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,11 @@
|
||||||
|
|
||||||
package org.keycloak.keys;
|
package org.keycloak.keys;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
import org.keycloak.component.ComponentFactory;
|
import org.keycloak.component.ComponentFactory;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @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);
|
T create(KeycloakSession session, ComponentModel model);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void close() {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
|
@ -17,21 +17,19 @@
|
||||||
|
|
||||||
package org.keycloak.keys;
|
package org.keycloak.keys;
|
||||||
|
|
||||||
|
import org.keycloak.crypto.KeyStatus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public abstract class KeyMetadata {
|
public abstract class KeyMetadata {
|
||||||
|
|
||||||
public enum Status {
|
|
||||||
ACTIVE, PASSIVE, DISABLED
|
|
||||||
}
|
|
||||||
|
|
||||||
private String providerId;
|
private String providerId;
|
||||||
private long providerPriority;
|
private long providerPriority;
|
||||||
|
|
||||||
private String kid;
|
private String kid;
|
||||||
|
|
||||||
private Status status;
|
private KeyStatus status;
|
||||||
|
|
||||||
public String getProviderId() {
|
public String getProviderId() {
|
||||||
return providerId;
|
return providerId;
|
||||||
|
@ -57,11 +55,11 @@ public abstract class KeyMetadata {
|
||||||
this.kid = kid;
|
this.kid = kid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Status getStatus() {
|
public KeyStatus getStatus() {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStatus(Status status) {
|
public void setStatus(KeyStatus status) {
|
||||||
this.status = status;
|
this.status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
package org.keycloak.models;
|
package org.keycloak.models;
|
||||||
|
|
||||||
|
import org.keycloak.crypto.KeyUse;
|
||||||
|
import org.keycloak.crypto.KeyWrapper;
|
||||||
import org.keycloak.keys.SecretKeyMetadata;
|
import org.keycloak.keys.SecretKeyMetadata;
|
||||||
import org.keycloak.keys.RsaKeyMetadata;
|
import org.keycloak.keys.RsaKeyMetadata;
|
||||||
|
|
||||||
|
@ -32,25 +34,43 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
public interface KeyManager {
|
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);
|
ActiveRsaKey getActiveRsaKey(RealmModel realm);
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
PublicKey getRsaPublicKey(RealmModel realm, String kid);
|
PublicKey getRsaPublicKey(RealmModel realm, String kid);
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
Certificate getRsaCertificate(RealmModel realm, String kid);
|
Certificate getRsaCertificate(RealmModel realm, String kid);
|
||||||
|
|
||||||
List<RsaKeyMetadata> getRsaKeys(RealmModel realm, boolean includeDisabled);
|
@Deprecated
|
||||||
|
List<RsaKeyMetadata> getRsaKeys(RealmModel realm);
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
ActiveHmacKey getActiveHmacKey(RealmModel realm);
|
ActiveHmacKey getActiveHmacKey(RealmModel realm);
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
SecretKey getHmacSecretKey(RealmModel realm, String kid);
|
SecretKey getHmacSecretKey(RealmModel realm, String kid);
|
||||||
|
|
||||||
List<SecretKeyMetadata> getHmacKeys(RealmModel realm, boolean includeDisabled);
|
@Deprecated
|
||||||
|
List<SecretKeyMetadata> getHmacKeys(RealmModel realm);
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
ActiveAesKey getActiveAesKey(RealmModel realm);
|
ActiveAesKey getActiveAesKey(RealmModel realm);
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
SecretKey getAesSecretKey(RealmModel realm, String kid);
|
SecretKey getAesSecretKey(RealmModel realm, String kid);
|
||||||
|
|
||||||
List<SecretKeyMetadata> getAesKeys(RealmModel realm, boolean includeDisabled);
|
@Deprecated
|
||||||
|
List<SecretKeyMetadata> getAesKeys(RealmModel realm);
|
||||||
|
|
||||||
class ActiveRsaKey {
|
class ActiveRsaKey {
|
||||||
private final String kid;
|
private final String kid;
|
||||||
|
|
|
@ -17,33 +17,25 @@
|
||||||
package org.keycloak.broker.saml;
|
package org.keycloak.broker.saml;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.broker.provider.AbstractIdentityProvider;
|
import org.keycloak.broker.provider.*;
|
||||||
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.util.SimpleHttp;
|
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||||
import org.keycloak.common.util.PemUtils;
|
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.AssertionType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
|
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.NameIDType;
|
import org.keycloak.dom.saml.v2.assertion.NameIDType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.SubjectType;
|
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.dom.saml.v2.protocol.ResponseType;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.keys.RsaKeyMetadata;
|
import org.keycloak.keys.RsaKeyMetadata;
|
||||||
import org.keycloak.models.FederatedIdentityModel;
|
import org.keycloak.models.*;
|
||||||
import org.keycloak.models.KeyManager;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
import org.keycloak.models.UserSessionModel;
|
|
||||||
import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder;
|
import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder;
|
||||||
import org.keycloak.saml.SAML2AuthnRequestBuilder;
|
import org.keycloak.saml.*;
|
||||||
import org.keycloak.saml.SAML2LogoutRequestBuilder;
|
|
||||||
import org.keycloak.saml.SAML2NameIDPolicyBuilder;
|
|
||||||
import org.keycloak.saml.SPMetadataDescriptor;
|
|
||||||
import org.keycloak.saml.SignatureAlgorithm;
|
|
||||||
import org.keycloak.saml.common.constants.GeneralConstants;
|
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
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.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
@ -52,11 +44,6 @@ import javax.ws.rs.core.UriInfo;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
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
|
* @author Pedro Igor
|
||||||
|
@ -246,12 +233,12 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
||||||
StringBuilder encryptionKeysString = new StringBuilder();
|
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
|
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() == KeyStatus.PASSIVE ? 1 : -1));
|
||||||
keys.addAll(session.keys().getRsaKeys(realm, false));
|
keys.addAll(session.keys().getRsaKeys(realm));
|
||||||
for (RsaKeyMetadata key : keys) {
|
for (RsaKeyMetadata key : keys) {
|
||||||
addKeyInfo(signingKeysString, key, KeyTypes.SIGNING.value());
|
addKeyInfo(signingKeysString, key, KeyTypes.SIGNING.value());
|
||||||
|
|
||||||
if (key.getStatus() == Status.ACTIVE) {
|
if (key.getStatus() == KeyStatus.ACTIVE) {
|
||||||
addKeyInfo(encryptionKeysString, key, KeyTypes.ENCRYPTION.value());
|
addKeyInfo(encryptionKeysString, key, KeyTypes.ENCRYPTION.value());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,13 +17,12 @@
|
||||||
|
|
||||||
package org.keycloak.keys;
|
package org.keycloak.keys;
|
||||||
|
|
||||||
|
import org.keycloak.common.util.KeyUtils;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
import org.keycloak.jose.jws.AlgorithmType;
|
import org.keycloak.crypto.*;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -31,110 +30,49 @@ 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 RsaKeyProvider {
|
public abstract class AbstractRsaKeyProvider implements KeyProvider {
|
||||||
|
|
||||||
private final boolean enabled;
|
private final KeyStatus status;
|
||||||
|
|
||||||
private final boolean active;
|
|
||||||
|
|
||||||
private final ComponentModel model;
|
private final ComponentModel model;
|
||||||
|
|
||||||
private final Keys keys;
|
private final KeyWrapper key;
|
||||||
|
|
||||||
public AbstractRsaKeyProvider(RealmModel realm, ComponentModel model) {
|
public AbstractRsaKeyProvider(RealmModel realm, ComponentModel model) {
|
||||||
this.model = 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);
|
if (model.hasNote(KeyWrapper.class.getName())) {
|
||||||
this.active = model.get(Attributes.ACTIVE_KEY, true);
|
key = model.getNote(KeyWrapper.class.getName());
|
||||||
|
|
||||||
if (model.hasNote(Keys.class.getName())) {
|
|
||||||
keys = model.getNote(Keys.class.getName());
|
|
||||||
} else {
|
} else {
|
||||||
keys = loadKeys(realm, model);
|
key = loadKey(realm, model);
|
||||||
model.setNote(Keys.class.getName(), keys);
|
model.setNote(KeyWrapper.class.getName(), key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Keys loadKeys(RealmModel realm, ComponentModel model);
|
protected abstract KeyWrapper loadKey(RealmModel realm, ComponentModel model);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final String getKid() {
|
public List<KeyWrapper> getKeys() {
|
||||||
return isActive() ? keys.getKid() : null;
|
return Collections.singletonList(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected KeyWrapper createKeyWrapper(KeyPair keyPair, X509Certificate certificate) {
|
||||||
public final PrivateKey getPrivateKey() {
|
KeyWrapper key = new KeyWrapper();
|
||||||
return isActive() ? keys.getKeyPair().getPrivate() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
key.setProviderId(model.getId());
|
||||||
public final PublicKey getPublicKey(String kid) {
|
key.setProviderPriority(model.get("priority", 0l));
|
||||||
return isEnabled() && kid.equals(keys.getKid()) ? keys.getKeyPair().getPublic() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
key.setKid(KeyUtils.createKeyId(keyPair.getPublic()));
|
||||||
public X509Certificate getCertificate(String kid) {
|
key.setUse(KeyUse.SIG);
|
||||||
return isEnabled() && kid.equals(keys.getKid()) ? keys.getCertificate() : null;
|
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
|
return key;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 RsaKeyProviderFactory {
|
public abstract class AbstractRsaKeyProviderFactory implements KeyProviderFactory {
|
||||||
|
|
||||||
public final static ProviderConfigurationBuilder configurationBuilder() {
|
public final static ProviderConfigurationBuilder configurationBuilder() {
|
||||||
return ProviderConfigurationBuilder.create()
|
return ProviderConfigurationBuilder.create()
|
||||||
|
|
|
@ -19,20 +19,19 @@ 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.crypto.Algorithm;
|
||||||
|
import org.keycloak.crypto.KeyUse;
|
||||||
|
import org.keycloak.crypto.KeyWrapper;
|
||||||
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 javax.crypto.SecretKey;
|
||||||
|
import java.security.PrivateKey;
|
||||||
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.*;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -49,204 +48,172 @@ public class DefaultKeyManager implements KeyManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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) {
|
public ActiveRsaKey getActiveRsaKey(RealmModel realm) {
|
||||||
for (KeyProvider p : getProviders(realm)) {
|
KeyWrapper key = getActiveKey(realm, KeyUse.SIG, Algorithm.RS256);
|
||||||
if (p.getType().equals(AlgorithmType.RSA)) {
|
return new ActiveRsaKey(key.getKid(), (PrivateKey) key.getSignKey(), (PublicKey) key.getVerifyKey(), key.getCertificate());
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated
|
||||||
public ActiveHmacKey getActiveHmacKey(RealmModel realm) {
|
public ActiveHmacKey getActiveHmacKey(RealmModel realm) {
|
||||||
for (KeyProvider p : getProviders(realm)) {
|
KeyWrapper key = getActiveKey(realm, KeyUse.SIG, Algorithm.HS256);
|
||||||
if (p.getType().equals(AlgorithmType.HMAC)) {
|
return new ActiveHmacKey(key.getKid(), key.getSecretKey());
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated
|
||||||
public ActiveAesKey getActiveAesKey(RealmModel realm) {
|
public ActiveAesKey getActiveAesKey(RealmModel realm) {
|
||||||
for (KeyProvider p : getProviders(realm)) {
|
KeyWrapper key = getActiveKey(realm, KeyUse.ENC, Algorithm.AES);
|
||||||
if (p.getType().equals(AlgorithmType.AES)) {
|
return new ActiveAesKey(key.getKid(), key.getSecretKey());
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated
|
||||||
public PublicKey getRsaPublicKey(RealmModel realm, String kid) {
|
public PublicKey getRsaPublicKey(RealmModel realm, String kid) {
|
||||||
if (kid == null) {
|
KeyWrapper key = getKey(realm, kid, KeyUse.SIG, Algorithm.RS256);
|
||||||
logger.warnv("KID is null, can't find public key", realm.getName(), kid);
|
return key != null ? (PublicKey) key.getVerifyKey() : null;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated
|
||||||
public Certificate getRsaCertificate(RealmModel realm, String kid) {
|
public Certificate getRsaCertificate(RealmModel realm, String kid) {
|
||||||
if (kid == null) {
|
KeyWrapper key = getKey(realm, kid, KeyUse.SIG, Algorithm.RS256);
|
||||||
logger.warnv("KID is null, can't find public key", realm.getName(), kid);
|
return key != null ? key.getCertificate() : null;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated
|
||||||
public SecretKey getHmacSecretKey(RealmModel realm, String kid) {
|
public SecretKey getHmacSecretKey(RealmModel realm, String kid) {
|
||||||
if (kid == null) {
|
KeyWrapper key = getKey(realm, kid, KeyUse.SIG, Algorithm.HS256);
|
||||||
logger.warnv("KID is null, can't find secret key", realm.getName(), kid);
|
return key != null ? key.getSecretKey() : null;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated
|
||||||
public SecretKey getAesSecretKey(RealmModel realm, String kid) {
|
public SecretKey getAesSecretKey(RealmModel realm, String kid) {
|
||||||
if (kid == null) {
|
KeyWrapper key = getKey(realm, kid, KeyUse.ENC, Algorithm.AES);
|
||||||
logger.warnv("KID is null, can't find aes key", realm.getName(), kid);
|
return key.getSecretKey();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<RsaKeyMetadata> getRsaKeys(RealmModel realm, boolean includeDisabled) {
|
@Deprecated
|
||||||
|
public List<RsaKeyMetadata> getRsaKeys(RealmModel realm) {
|
||||||
List<RsaKeyMetadata> keys = new LinkedList<>();
|
List<RsaKeyMetadata> keys = new LinkedList<>();
|
||||||
for (KeyProvider p : getProviders(realm)) {
|
for (KeyWrapper key : getKeys(realm, KeyUse.SIG, Algorithm.RS256)) {
|
||||||
if (p instanceof RsaKeyProvider) {
|
RsaKeyMetadata m = new RsaKeyMetadata();
|
||||||
if (includeDisabled) {
|
m.setCertificate(key.getCertificate());
|
||||||
keys.addAll(p.getKeyMetadata());
|
m.setPublicKey((PublicKey) key.getVerifyKey());
|
||||||
} else {
|
m.setKid(key.getKid());
|
||||||
List<RsaKeyMetadata> metadata = p.getKeyMetadata();
|
m.setProviderId(key.getProviderId());
|
||||||
metadata.stream().filter(k -> k.getStatus() != KeyMetadata.Status.DISABLED).forEach(k -> keys.add(k));
|
m.setProviderPriority(key.getProviderPriority());
|
||||||
}
|
m.setStatus(key.getStatus());
|
||||||
}
|
|
||||||
|
keys.add(m);
|
||||||
}
|
}
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<SecretKeyMetadata> getHmacKeys(RealmModel realm, boolean includeDisabled) {
|
public List<SecretKeyMetadata> getHmacKeys(RealmModel realm) {
|
||||||
List<SecretKeyMetadata> keys = new LinkedList<>();
|
List<SecretKeyMetadata> keys = new LinkedList<>();
|
||||||
for (KeyProvider p : getProviders(realm)) {
|
for (KeyWrapper key : getKeys(realm, KeyUse.SIG, Algorithm.HS256)) {
|
||||||
if (p instanceof HmacKeyProvider) {
|
SecretKeyMetadata m = new SecretKeyMetadata();
|
||||||
if (includeDisabled) {
|
m.setKid(key.getKid());
|
||||||
keys.addAll(p.getKeyMetadata());
|
m.setProviderId(key.getProviderId());
|
||||||
} else {
|
m.setProviderPriority(key.getProviderPriority());
|
||||||
List<SecretKeyMetadata> metadata = p.getKeyMetadata();
|
m.setStatus(key.getStatus());
|
||||||
metadata.stream().filter(k -> k.getStatus() != KeyMetadata.Status.DISABLED).forEach(k -> keys.add(k));
|
|
||||||
}
|
keys.add(m);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<SecretKeyMetadata> getAesKeys(RealmModel realm, boolean includeDisabled) {
|
public List<SecretKeyMetadata> getAesKeys(RealmModel realm) {
|
||||||
List<SecretKeyMetadata> keys = new LinkedList<>();
|
List<SecretKeyMetadata> keys = new LinkedList<>();
|
||||||
for (KeyProvider p : getProviders(realm)) {
|
for (KeyWrapper key : getKeys(realm, KeyUse.ENC, Algorithm.AES)) {
|
||||||
if (p instanceof AesKeyProvider) {
|
SecretKeyMetadata m = new SecretKeyMetadata();
|
||||||
if (includeDisabled) {
|
m.setKid(key.getKid());
|
||||||
keys.addAll(p.getKeyMetadata());
|
m.setProviderId(key.getProviderId());
|
||||||
} else {
|
m.setProviderPriority(key.getProviderPriority());
|
||||||
List<SecretKeyMetadata> metadata = p.getKeyMetadata();
|
m.setStatus(key.getStatus());
|
||||||
metadata.stream().filter(k -> k.getStatus() != KeyMetadata.Status.DISABLED).forEach(k -> keys.add(k));
|
|
||||||
}
|
keys.add(m);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return keys;
|
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) {
|
private List<KeyProvider> getProviders(RealmModel realm) {
|
||||||
List<KeyProvider> providers = providersMap.get(realm.getId());
|
List<KeyProvider> providers = providersMap.get(realm.getId());
|
||||||
if (providers == null) {
|
if (providers == null) {
|
||||||
|
@ -255,10 +222,6 @@ 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;
|
|
||||||
boolean activeAes = 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());
|
||||||
|
@ -266,41 +229,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 (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) {
|
} catch (Throwable t) {
|
||||||
logger.errorv(t, "Failed to load provider {0}", c.getId());
|
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());
|
providers.add(new FailsafeRsaKeyProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!activeHmac) {
|
try {
|
||||||
|
getActiveKey(realm, KeyUse.SIG, Algorithm.HS256);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
providers.add(new FailsafeHmacKeyProvider());
|
providers.add(new FailsafeHmacKeyProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!activeAes) {
|
try {
|
||||||
|
getActiveKey(realm, KeyUse.ENC, Algorithm.AES);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
providers.add(new FailsafeAesKeyProvider());
|
providers.add(new FailsafeAesKeyProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
providersMap.put(realm.getId(), providers);
|
|
||||||
}
|
}
|
||||||
return providers;
|
return providers;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,14 +18,32 @@
|
||||||
package org.keycloak.keys;
|
package org.keycloak.keys;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
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>
|
* @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);
|
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
|
@Override
|
||||||
protected Logger logger() {
|
protected Logger logger() {
|
||||||
return logger;
|
return logger;
|
||||||
|
|
|
@ -20,6 +20,10 @@ package org.keycloak.keys;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.common.util.KeyUtils;
|
import org.keycloak.common.util.KeyUtils;
|
||||||
import org.keycloak.common.util.Time;
|
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 org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
@ -29,12 +33,28 @@ 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 FailsafeHmacKeyProvider extends FailsafeSecretKeyProvider implements HmacKeyProvider {
|
public class FailsafeHmacKeyProvider extends FailsafeSecretKeyProvider {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(FailsafeHmacKeyProvider.class);
|
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
|
@Override
|
||||||
protected Logger logger() {
|
protected Logger logger() {
|
||||||
return logger;
|
return logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,77 +20,62 @@ package org.keycloak.keys;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.common.util.KeyUtils;
|
import org.keycloak.common.util.KeyUtils;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.crypto.*;
|
||||||
|
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
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 FailsafeRsaKeyProvider implements RsaKeyProvider {
|
public class FailsafeRsaKeyProvider implements KeyProvider {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(FailsafeRsaKeyProvider.class);
|
private static final Logger logger = Logger.getLogger(FailsafeRsaKeyProvider.class);
|
||||||
|
|
||||||
private static String KID;
|
private static KeyWrapper KEY;
|
||||||
|
|
||||||
private static KeyPair KEY_PAIR;
|
|
||||||
|
|
||||||
private static long EXPIRES;
|
private static long EXPIRES;
|
||||||
|
|
||||||
private KeyPair keyPair;
|
private KeyWrapper key;
|
||||||
|
|
||||||
private String kid;
|
|
||||||
|
|
||||||
public FailsafeRsaKeyProvider() {
|
public FailsafeRsaKeyProvider() {
|
||||||
logger.errorv("No active keys found, using failsafe provider, please login to admin console to add keys. Clustering is not supported.");
|
logger.errorv("No active keys found, using failsafe provider, please login to admin console to add keys. Clustering is not supported.");
|
||||||
|
|
||||||
synchronized (FailsafeRsaKeyProvider.class) {
|
synchronized (FailsafeRsaKeyProvider.class) {
|
||||||
if (EXPIRES < Time.currentTime()) {
|
if (EXPIRES < Time.currentTime()) {
|
||||||
KEY_PAIR = KeyUtils.generateRsaKeyPair(2048);
|
KEY = createKeyWrapper();
|
||||||
KID = KeyUtils.createKeyId(KEY_PAIR.getPublic());
|
|
||||||
EXPIRES = Time.currentTime() + 60 * 10;
|
EXPIRES = Time.currentTime() + 60 * 10;
|
||||||
|
|
||||||
if (EXPIRES > 0) {
|
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;
|
||||||
keyPair = KEY_PAIR;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getKid() {
|
public List<KeyWrapper> getKeys() {
|
||||||
return kid;
|
return Collections.singletonList(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private KeyWrapper createKeyWrapper() {
|
||||||
public PrivateKey getPrivateKey() {
|
KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
|
||||||
return keyPair.getPrivate();
|
|
||||||
|
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() {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,74 +17,72 @@
|
||||||
|
|
||||||
package org.keycloak.keys;
|
package org.keycloak.keys;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.common.util.KeyUtils;
|
import org.keycloak.common.util.KeyUtils;
|
||||||
import org.keycloak.common.util.Time;
|
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 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>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public abstract class FailsafeSecretKeyProvider implements SecretKeyProvider {
|
public abstract class FailsafeSecretKeyProvider implements KeyProvider {
|
||||||
|
|
||||||
|
private static KeyWrapper KEY;
|
||||||
private static String KID;
|
|
||||||
|
|
||||||
private static SecretKey KEY;
|
|
||||||
|
|
||||||
private static long EXPIRES;
|
private static long EXPIRES;
|
||||||
|
|
||||||
private SecretKey key;
|
private KeyWrapper key;
|
||||||
|
|
||||||
private String kid;
|
|
||||||
|
|
||||||
public FailsafeSecretKeyProvider() {
|
public FailsafeSecretKeyProvider() {
|
||||||
logger().errorv("No active keys found, using failsafe provider, please login to admin console to add keys. Clustering is not supported.");
|
logger().errorv("No active keys found, using failsafe provider, please login to admin console to add keys. Clustering is not supported.");
|
||||||
|
|
||||||
synchronized (FailsafeHmacKeyProvider.class) {
|
synchronized (FailsafeHmacKeyProvider.class) {
|
||||||
if (EXPIRES < Time.currentTime()) {
|
if (EXPIRES < Time.currentTime()) {
|
||||||
KEY = KeyUtils.loadSecretKey(KeycloakModelUtils.generateSecret(32), getJavaAlgorithmName());
|
KEY = createKeyWrapper();
|
||||||
KID = KeycloakModelUtils.generateId();
|
|
||||||
EXPIRES = Time.currentTime() + 60 * 10;
|
EXPIRES = Time.currentTime() + 60 * 10;
|
||||||
|
|
||||||
if (EXPIRES > 0) {
|
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;
|
key = KEY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getKid() {
|
public List<KeyWrapper> getKeys() {
|
||||||
return kid;
|
return Collections.singletonList(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private KeyWrapper createKeyWrapper() {
|
||||||
public SecretKey getSecretKey() {
|
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;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected abstract KeyUse getUse();
|
||||||
public SecretKey getSecretKey(String kid) {
|
|
||||||
return kid.equals(this.kid) ? key : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
protected abstract String getType();
|
||||||
public List<SecretKeyMetadata> getKeyMetadata() {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
protected abstract String getAlgorithm();
|
||||||
public void close() {
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract Logger logger();
|
protected abstract Logger logger();
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,14 +18,17 @@
|
||||||
package org.keycloak.keys;
|
package org.keycloak.keys;
|
||||||
|
|
||||||
import org.keycloak.component.ComponentModel;
|
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>
|
* @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) {
|
public GeneratedAesKeyProvider(ComponentModel model) {
|
||||||
super(model);
|
super(model, KeyUse.ENC, KeyType.OCT, Algorithm.AES);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import java.util.List;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.crypto.KeyUse;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
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>
|
* @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);
|
private static final Logger logger = Logger.getLogger(GeneratedAesKeyProviderFactory.class);
|
||||||
|
|
||||||
|
@ -52,7 +53,7 @@ public class GeneratedAesKeyProviderFactory extends GeneratedSecretKeyProviderFa
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AesKeyProvider create(KeycloakSession session, ComponentModel model) {
|
public GeneratedAesKeyProvider create(KeycloakSession session, ComponentModel model) {
|
||||||
return new GeneratedAesKeyProvider(model);
|
return new GeneratedAesKeyProvider(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,15 +18,18 @@
|
||||||
package org.keycloak.keys;
|
package org.keycloak.keys;
|
||||||
|
|
||||||
import org.keycloak.component.ComponentModel;
|
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>
|
* @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) {
|
public GeneratedHmacKeyProvider(ComponentModel model) {
|
||||||
super(model);
|
super(model, KeyUse.SIG, KeyType.OCT, Algorithm.HS256);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,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 GeneratedHmacKeyProviderFactory extends GeneratedSecretKeyProviderFactory<HmacKeyProvider> implements HmacKeyProviderFactory {
|
public class GeneratedHmacKeyProviderFactory extends GeneratedSecretKeyProviderFactory<GeneratedHmacKeyProvider> {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(GeneratedHmacKeyProviderFactory.class);
|
private static final Logger logger = Logger.getLogger(GeneratedHmacKeyProviderFactory.class);
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ public class GeneratedHmacKeyProviderFactory extends GeneratedSecretKeyProviderF
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HmacKeyProvider create(KeycloakSession session, ComponentModel model) {
|
public GeneratedHmacKeyProvider create(KeycloakSession session, ComponentModel model) {
|
||||||
return new GeneratedHmacKeyProvider(model);
|
return new GeneratedHmacKeyProvider(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -110,18 +110,6 @@ public class GeneratedRsaKeyProviderFactory extends AbstractRsaKeyProviderFactor
|
||||||
return CONFIG_PROPERTIES;
|
return CONFIG_PROPERTIES;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(Config.Scope config) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void postInit(KeycloakSessionFactory factory) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return ID;
|
return ID;
|
||||||
|
|
|
@ -17,86 +17,67 @@
|
||||||
|
|
||||||
package org.keycloak.keys;
|
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.Base64Url;
|
||||||
import org.keycloak.common.util.KeyUtils;
|
import org.keycloak.common.util.KeyUtils;
|
||||||
import org.keycloak.component.ComponentModel;
|
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>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public abstract class GeneratedSecretKeyProvider implements SecretKeyProvider {
|
public abstract class GeneratedSecretKeyProvider implements KeyProvider {
|
||||||
|
|
||||||
private final boolean enabled;
|
|
||||||
|
|
||||||
private final boolean active;
|
|
||||||
|
|
||||||
|
private final KeyStatus status;
|
||||||
private final ComponentModel model;
|
private final ComponentModel model;
|
||||||
private final String kid;
|
private final String kid;
|
||||||
private final SecretKey secretKey;
|
private final SecretKey secretKey;
|
||||||
|
private final KeyUse use;
|
||||||
|
private String type;
|
||||||
|
private final String algorithm;
|
||||||
|
|
||||||
public GeneratedSecretKeyProvider(ComponentModel model) {
|
public GeneratedSecretKeyProvider(ComponentModel model, KeyUse use, String type, String algorithm) {
|
||||||
this.enabled = model.get(Attributes.ENABLED_KEY, true);
|
this.status = KeyStatus.from(model.get(Attributes.ACTIVE_KEY, true), model.get(Attributes.ENABLED_KEY, true));
|
||||||
this.active = model.get(Attributes.ACTIVE_KEY, true);
|
|
||||||
this.kid = model.get(Attributes.KID_KEY);
|
this.kid = model.get(Attributes.KID_KEY);
|
||||||
this.model = model;
|
this.model = model;
|
||||||
|
this.use = use;
|
||||||
|
this.type = type;
|
||||||
|
this.algorithm = algorithm;
|
||||||
|
|
||||||
if (model.hasNote(SecretKey.class.getName())) {
|
if (model.hasNote(SecretKey.class.getName())) {
|
||||||
secretKey = model.getNote(SecretKey.class.getName());
|
secretKey = model.getNote(SecretKey.class.getName());
|
||||||
} else {
|
} 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);
|
model.setNote(SecretKey.class.getName(), secretKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SecretKey getSecretKey() {
|
public List<KeyWrapper> getKeys() {
|
||||||
return isActive() ? secretKey : null;
|
KeyWrapper key = new KeyWrapper();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
key.setProviderId(model.getId());
|
||||||
public SecretKey getSecretKey(String kid) {
|
key.setProviderPriority(model.get("priority", 0l));
|
||||||
return isEnabled() && kid.equals(this.kid) ? secretKey : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
key.setKid(kid);
|
||||||
public String getKid() {
|
key.setUse(use);
|
||||||
return isActive() ? kid : null;
|
key.setType(type);
|
||||||
}
|
key.setAlgorithms(algorithm);
|
||||||
|
key.setStatus(status);
|
||||||
|
key.setSecretKey(secretKey);
|
||||||
|
|
||||||
@Override
|
return Collections.singletonList(key);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isEnabled() {
|
|
||||||
return secretKey != null && enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isActive() {
|
|
||||||
return isEnabled() && active;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,17 +66,5 @@ public abstract class GeneratedSecretKeyProviderFactory<T extends KeyProvider> i
|
||||||
|
|
||||||
protected abstract Logger logger();
|
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();
|
protected abstract int getDefaultKeySize();
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.keys;
|
||||||
import org.keycloak.common.util.KeyUtils;
|
import org.keycloak.common.util.KeyUtils;
|
||||||
import org.keycloak.common.util.PemUtils;
|
import org.keycloak.common.util.PemUtils;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.crypto.KeyWrapper;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
|
@ -38,7 +39,7 @@ public class ImportedRsaKeyProvider extends AbstractRsaKeyProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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 privateRsaKeyPem = model.getConfig().getFirst(Attributes.PRIVATE_KEY_KEY);
|
||||||
String certificatePem = model.getConfig().getFirst(Attributes.CERTIFICATE_KEY);
|
String certificatePem = model.getConfig().getFirst(Attributes.CERTIFICATE_KEY);
|
||||||
|
|
||||||
|
@ -48,9 +49,7 @@ public class ImportedRsaKeyProvider extends AbstractRsaKeyProvider {
|
||||||
KeyPair keyPair = new KeyPair(publicKey, privateKey);
|
KeyPair keyPair = new KeyPair(publicKey, privateKey);
|
||||||
X509Certificate certificate = PemUtils.decodeCertificate(certificatePem);
|
X509Certificate certificate = PemUtils.decodeCertificate(certificatePem);
|
||||||
|
|
||||||
String kid = KeyUtils.createKeyId(keyPair.getPublic());
|
return createKeyWrapper(keyPair, certificate);
|
||||||
|
|
||||||
return new Keys(kid, keyPair, certificate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,14 +17,12 @@
|
||||||
|
|
||||||
package org.keycloak.keys;
|
package org.keycloak.keys;
|
||||||
|
|
||||||
import org.keycloak.Config;
|
|
||||||
import org.keycloak.common.util.CertificateUtils;
|
import org.keycloak.common.util.CertificateUtils;
|
||||||
import org.keycloak.common.util.KeyUtils;
|
import org.keycloak.common.util.KeyUtils;
|
||||||
import org.keycloak.common.util.PemUtils;
|
import org.keycloak.common.util.PemUtils;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
import org.keycloak.component.ComponentValidationException;
|
import org.keycloak.component.ComponentValidationException;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.provider.ConfigurationValidationHelper;
|
import org.keycloak.provider.ConfigurationValidationHelper;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
@ -106,18 +104,6 @@ public class ImportedRsaKeyProviderFactory extends AbstractRsaKeyProviderFactory
|
||||||
return CONFIG_PROPERTIES;
|
return CONFIG_PROPERTIES;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(Config.Scope config) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void postInit(KeycloakSessionFactory factory) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return ID;
|
return ID;
|
||||||
|
|
|
@ -17,23 +17,16 @@
|
||||||
|
|
||||||
package org.keycloak.keys;
|
package org.keycloak.keys;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.common.util.CertificateUtils;
|
import org.keycloak.common.util.CertificateUtils;
|
||||||
import org.keycloak.common.util.KeyUtils;
|
import org.keycloak.common.util.KeyUtils;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.crypto.KeyWrapper;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.KeyPair;
|
import java.security.*;
|
||||||
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.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
@ -42,14 +35,12 @@ import java.security.cert.X509Certificate;
|
||||||
*/
|
*/
|
||||||
public class JavaKeystoreKeyProvider extends AbstractRsaKeyProvider {
|
public class JavaKeystoreKeyProvider extends AbstractRsaKeyProvider {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(JavaKeystoreKeyProvider.class);
|
|
||||||
|
|
||||||
public JavaKeystoreKeyProvider(RealmModel realm, ComponentModel model) {
|
public JavaKeystoreKeyProvider(RealmModel realm, ComponentModel model) {
|
||||||
super(realm, model);
|
super(realm, model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Keys loadKeys(RealmModel realm, ComponentModel model) {
|
protected KeyWrapper loadKey(RealmModel realm, ComponentModel model) {
|
||||||
try {
|
try {
|
||||||
KeyStore keyStore = KeyStore.getInstance("JKS");
|
KeyStore keyStore = KeyStore.getInstance("JKS");
|
||||||
keyStore.load(new FileInputStream(model.get(JavaKeystoreKeyProviderFactory.KEYSTORE_KEY)), model.get(JavaKeystoreKeyProviderFactory.KEYSTORE_PASSWORD_KEY).toCharArray());
|
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());
|
certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
String kid = KeyUtils.createKeyId(keyPair.getPublic());
|
return createKeyWrapper(keyPair, certificate);
|
||||||
|
|
||||||
return new Keys(kid, keyPair, certificate);
|
|
||||||
} catch (KeyStoreException kse) {
|
} catch (KeyStoreException kse) {
|
||||||
throw new RuntimeException("KeyStore error on server. " + kse.getMessage(), kse);
|
throw new RuntimeException("KeyStore error on server. " + kse.getMessage(), kse);
|
||||||
} catch (FileNotFoundException fnfe) {
|
} catch (FileNotFoundException fnfe) {
|
||||||
|
|
|
@ -77,7 +77,7 @@ public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactor
|
||||||
|
|
||||||
try {
|
try {
|
||||||
new JavaKeystoreKeyProvider(session.getContext().getRealm(), model)
|
new JavaKeystoreKeyProvider(session.getContext().getRealm(), model)
|
||||||
.loadKeys(session.getContext().getRealm(), model);
|
.loadKey(session.getContext().getRealm(), model);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
logger.error("Failed to load keys.", t);
|
logger.error("Failed to load keys.", t);
|
||||||
throw new ComponentValidationException("Failed to load keys. " + t.getMessage(), t);
|
throw new ComponentValidationException("Failed to load keys. " + t.getMessage(), t);
|
||||||
|
@ -94,18 +94,6 @@ public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactor
|
||||||
return CONFIG_PROPERTIES;
|
return CONFIG_PROPERTIES;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(Config.Scope config) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void postInit(KeycloakSessionFactory factory) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return ID;
|
return ID;
|
||||||
|
|
|
@ -26,7 +26,6 @@ import org.keycloak.forms.login.LoginFormsProvider;
|
||||||
import org.keycloak.jose.jwk.JSONWebKeySet;
|
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||||
import org.keycloak.jose.jwk.JWK;
|
import org.keycloak.jose.jwk.JWK;
|
||||||
import org.keycloak.jose.jwk.JWKBuilder;
|
import org.keycloak.jose.jwk.JWKBuilder;
|
||||||
import org.keycloak.keys.KeyMetadata;
|
|
||||||
import org.keycloak.keys.RsaKeyMetadata;
|
import org.keycloak.keys.RsaKeyMetadata;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -195,7 +194,7 @@ public class OIDCLoginProtocolService {
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
@NoCache
|
@NoCache
|
||||||
public Response certs() {
|
public Response certs() {
|
||||||
List<RsaKeyMetadata> publicKeys = session.keys().getRsaKeys(realm, false);
|
List<RsaKeyMetadata> publicKeys = session.keys().getRsaKeys(realm);
|
||||||
JWK[] keys = new JWK[publicKeys.size()];
|
JWK[] keys = new JWK[publicKeys.size()];
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
import org.keycloak.common.VerificationException;
|
import org.keycloak.common.VerificationException;
|
||||||
import org.keycloak.common.util.PemUtils;
|
import org.keycloak.common.util.PemUtils;
|
||||||
import org.keycloak.common.util.StreamUtil;
|
import org.keycloak.common.util.StreamUtil;
|
||||||
|
import org.keycloak.crypto.KeyStatus;
|
||||||
import org.keycloak.dom.saml.v2.SAML2Object;
|
import org.keycloak.dom.saml.v2.SAML2Object;
|
||||||
import org.keycloak.dom.saml.v2.assertion.BaseIDAbstractType;
|
import org.keycloak.dom.saml.v2.assertion.BaseIDAbstractType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.NameIDType;
|
import org.keycloak.dom.saml.v2.assertion.NameIDType;
|
||||||
|
@ -80,7 +81,6 @@ import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import org.keycloak.common.util.StringPropertyReplacer;
|
import org.keycloak.common.util.StringPropertyReplacer;
|
||||||
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
|
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
|
||||||
import org.keycloak.keys.KeyMetadata;
|
|
||||||
import org.keycloak.rotation.HardcodedKeyLocator;
|
import org.keycloak.rotation.HardcodedKeyLocator;
|
||||||
import org.keycloak.rotation.KeyLocator;
|
import org.keycloak.rotation.KeyLocator;
|
||||||
import org.keycloak.saml.SPMetadataDescriptor;
|
import org.keycloak.saml.SPMetadataDescriptor;
|
||||||
|
@ -591,8 +591,8 @@ public class SamlService extends AuthorizationEndpointBase {
|
||||||
StringBuilder keysString = new StringBuilder();
|
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
|
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() == KeyStatus.PASSIVE ? 1 : -1));
|
||||||
keys.addAll(session.keys().getRsaKeys(realm, false));
|
keys.addAll(session.keys().getRsaKeys(realm));
|
||||||
for (RsaKeyMetadata key : keys) {
|
for (RsaKeyMetadata key : keys) {
|
||||||
addKeyInfo(keysString, key, KeyTypes.SIGNING.value());
|
addKeyInfo(keysString, key, KeyTypes.SIGNING.value());
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ 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.crypto.KeyStatus;
|
||||||
|
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
|
||||||
import org.keycloak.keys.RsaKeyMetadata;
|
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;
|
||||||
|
@ -27,6 +29,7 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.protocol.ClientInstallationProvider;
|
import org.keycloak.protocol.ClientInstallationProvider;
|
||||||
import org.keycloak.protocol.saml.SamlClient;
|
import org.keycloak.protocol.saml.SamlClient;
|
||||||
import org.keycloak.protocol.saml.SamlProtocol;
|
import org.keycloak.protocol.saml.SamlProtocol;
|
||||||
|
import org.keycloak.saml.SPMetadataDescriptor;
|
||||||
import org.keycloak.services.resources.RealmsResource;
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
|
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
@ -35,9 +38,6 @@ import javax.ws.rs.core.UriBuilder;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
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>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -90,8 +90,8 @@ public class SamlIDPDescriptorClientInstallation implements ClientInstallationPr
|
||||||
// keys
|
// 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
|
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() == KeyStatus.PASSIVE ? 1 : -1));
|
||||||
keys.addAll(session.keys().getRsaKeys(realm, false));
|
keys.addAll(session.keys().getRsaKeys(realm));
|
||||||
for (RsaKeyMetadata key : keys) {
|
for (RsaKeyMetadata key : keys) {
|
||||||
addKeyInfo(sb, key, KeyTypes.SIGNING.value());
|
addKeyInfo(sb, key, KeyTypes.SIGNING.value());
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +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.crypto.KeyWrapper;
|
||||||
import org.keycloak.jose.jws.AlgorithmType;
|
import org.keycloak.jose.jws.AlgorithmType;
|
||||||
import org.keycloak.keys.SecretKeyMetadata;
|
import org.keycloak.keys.SecretKeyMetadata;
|
||||||
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;
|
||||||
|
@ -58,48 +58,30 @@ public class KeyResource {
|
||||||
public KeysMetadataRepresentation getKeyMetadata() {
|
public KeysMetadataRepresentation getKeyMetadata() {
|
||||||
auth.realm().requireViewRealm();
|
auth.realm().requireViewRealm();
|
||||||
|
|
||||||
KeyManager keystore = session.keys();
|
|
||||||
|
|
||||||
KeysMetadataRepresentation keys = new KeysMetadataRepresentation();
|
KeysMetadataRepresentation keys = new KeysMetadataRepresentation();
|
||||||
|
keys.setKeys(new LinkedList<>());
|
||||||
|
keys.setActive(new HashMap<>());
|
||||||
|
|
||||||
Map<String, String> active = new HashMap<>();
|
for (KeyWrapper key : session.keys().getKeys(realm)) {
|
||||||
active.put(AlgorithmType.RSA.name(), keystore.getActiveRsaKey(realm).getKid());
|
KeysMetadataRepresentation.KeyMetadataRepresentation r = new KeysMetadataRepresentation.KeyMetadataRepresentation();
|
||||||
active.put(AlgorithmType.HMAC.name(), keystore.getActiveHmacKey(realm).getKid());
|
r.setProviderId(key.getProviderId());
|
||||||
active.put(AlgorithmType.AES.name(), keystore.getActiveAesKey(realm).getKid());
|
r.setProviderPriority(key.getProviderPriority());
|
||||||
keys.setActive(active);
|
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<>();
|
if (key.getStatus().isActive()) {
|
||||||
for (RsaKeyMetadata m : session.keys().getRsaKeys(realm, true)) {
|
for (String a : key.getAlgorithms()) {
|
||||||
KeysMetadataRepresentation.KeyMetadataRepresentation r = new KeysMetadataRepresentation.KeyMetadataRepresentation();
|
if (!keys.getActive().containsKey(a)) {
|
||||||
r.setProviderId(m.getProviderId());
|
keys.getActive().put(a, key.getKid());
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
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;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.keycloak.admin.client.resource.ClientScopeResource;
|
||||||
import org.keycloak.admin.client.resource.RealmResource;
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
import org.keycloak.admin.client.resource.RoleResource;
|
import org.keycloak.admin.client.resource.RoleResource;
|
||||||
import org.keycloak.admin.client.resource.UserResource;
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
|
import org.keycloak.crypto.Algorithm;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.ClientScopeRepresentation;
|
import org.keycloak.representations.idm.ClientScopeRepresentation;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
|
@ -255,7 +256,7 @@ public class ApiUtil {
|
||||||
|
|
||||||
public static KeysMetadataRepresentation.KeyMetadataRepresentation findActiveKey(RealmResource realm) {
|
public static KeysMetadataRepresentation.KeyMetadataRepresentation findActiveKey(RealmResource realm) {
|
||||||
KeysMetadataRepresentation keyMetadata = realm.keys().getKeyMetadata();
|
KeysMetadataRepresentation keyMetadata = realm.keys().getKeyMetadata();
|
||||||
String activeKid = keyMetadata.getActive().get("RSA");
|
String activeKid = keyMetadata.getActive().get(Algorithm.RS256);
|
||||||
for (KeysMetadataRepresentation.KeyMetadataRepresentation rep : keyMetadata.getKeys()) {
|
for (KeysMetadataRepresentation.KeyMetadataRepresentation rep : keyMetadata.getKeys()) {
|
||||||
if (rep.getKid().equals(activeKid)) {
|
if (rep.getKid().equals(activeKid)) {
|
||||||
return rep;
|
return rep;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.keycloak.testsuite.util;
|
package org.keycloak.testsuite.util;
|
||||||
|
|
||||||
import org.keycloak.common.util.BouncyIntegration;
|
import org.keycloak.common.util.BouncyIntegration;
|
||||||
|
import org.keycloak.representations.idm.KeysMetadataRepresentation;
|
||||||
|
|
||||||
import java.security.KeyFactory;
|
import java.security.KeyFactory;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
@ -39,4 +40,15 @@ public class KeyUtils {
|
||||||
throw new RuntimeException(e);
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ import org.keycloak.common.VerificationException;
|
||||||
import org.keycloak.common.util.KeystoreUtil;
|
import org.keycloak.common.util.KeystoreUtil;
|
||||||
import org.keycloak.common.util.PemUtils;
|
import org.keycloak.common.util.PemUtils;
|
||||||
import org.keycloak.constants.AdapterConstants;
|
import org.keycloak.constants.AdapterConstants;
|
||||||
|
import org.keycloak.crypto.Algorithm;
|
||||||
import org.keycloak.jose.jwk.JSONWebKeySet;
|
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
import org.keycloak.jose.jws.JWSInputException;
|
import org.keycloak.jose.jws.JWSInputException;
|
||||||
|
@ -1099,7 +1100,7 @@ public class OAuthClient {
|
||||||
public PublicKey getRealmPublicKey(String realm) {
|
public PublicKey getRealmPublicKey(String realm) {
|
||||||
if (!publicKeys.containsKey(realm)) {
|
if (!publicKeys.containsKey(realm)) {
|
||||||
KeysMetadataRepresentation keyMetadata = adminClient.realms().realm(realm).keys().getKeyMetadata();
|
KeysMetadataRepresentation keyMetadata = adminClient.realms().realm(realm).keys().getKeyMetadata();
|
||||||
String activeKid = keyMetadata.getActive().get("RSA");
|
String activeKid = keyMetadata.getActive().get(Algorithm.RS256);
|
||||||
PublicKey publicKey = null;
|
PublicKey publicKey = null;
|
||||||
for (KeysMetadataRepresentation.KeyMetadataRepresentation rep : keyMetadata.getKeys()) {
|
for (KeysMetadataRepresentation.KeyMetadataRepresentation rep : keyMetadata.getKeys()) {
|
||||||
if (rep.getKid().equals(activeKid)) {
|
if (rep.getKid().equals(activeKid)) {
|
||||||
|
|
|
@ -42,7 +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.crypto.Algorithm;
|
||||||
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;
|
||||||
|
@ -308,7 +308,7 @@ public class OIDCPublicKeyRotationAdapterTest extends AbstractServletsAdapterTes
|
||||||
|
|
||||||
private String getActiveKeyProvider() {
|
private String getActiveKeyProvider() {
|
||||||
KeysMetadataRepresentation keyMetadata = adminClient.realm(DEMO).keys().getKeyMetadata();
|
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()) {
|
for (KeysMetadataRepresentation.KeyMetadataRepresentation rep : keyMetadata.getKeys()) {
|
||||||
if (rep.getKid().equals(activeKid)) {
|
if (rep.getKid().equals(activeKid)) {
|
||||||
return rep.getProviderId();
|
return rep.getProviderId();
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.junit.Test;
|
||||||
import org.keycloak.admin.client.resource.RealmResource;
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
import org.keycloak.common.util.*;
|
import org.keycloak.common.util.*;
|
||||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||||
|
import org.keycloak.crypto.Algorithm;
|
||||||
import org.keycloak.keys.KeyProvider;
|
import org.keycloak.keys.KeyProvider;
|
||||||
import org.keycloak.keys.PublicKeyStorageUtils;
|
import org.keycloak.keys.PublicKeyStorageUtils;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||||
|
@ -293,7 +294,7 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
|
||||||
|
|
||||||
|
|
||||||
private void rotateKeys() {
|
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
|
// Rotate public keys on the parent broker
|
||||||
String realmId = providerRealm().toRepresentation().getId();
|
String realmId = providerRealm().toRepresentation().getId();
|
||||||
|
@ -308,7 +309,7 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
|
||||||
assertEquals(201, response.getStatus());
|
assertEquals(201, response.getStatus());
|
||||||
response.close();
|
response.close();
|
||||||
|
|
||||||
String updatedActiveKid = providerRealm().keys().getKeyMetadata().getActive().get("RSA");
|
String updatedActiveKid = providerRealm().keys().getKeyMetadata().getActive().get(Algorithm.RS256);
|
||||||
assertNotEquals(activeKid, updatedActiveKid);
|
assertNotEquals(activeKid, updatedActiveKid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,12 @@ package org.keycloak.testsuite.broker;
|
||||||
|
|
||||||
import org.keycloak.admin.client.resource.ClientResource;
|
import org.keycloak.admin.client.resource.ClientResource;
|
||||||
import org.keycloak.broker.saml.SAMLIdentityProviderConfig;
|
import org.keycloak.broker.saml.SAMLIdentityProviderConfig;
|
||||||
|
import org.keycloak.crypto.Algorithm;
|
||||||
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
|
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
|
||||||
import org.keycloak.protocol.saml.SamlConfigAttributes;
|
import org.keycloak.protocol.saml.SamlConfigAttributes;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||||
|
import org.keycloak.representations.idm.KeysMetadataRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||||
import org.keycloak.saml.common.util.DocumentUtil;
|
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.saml.AbstractSamlTest;
|
||||||
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
|
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
|
||||||
import org.keycloak.testsuite.updaters.IdentityProviderAttributeUpdater;
|
import org.keycloak.testsuite.updaters.IdentityProviderAttributeUpdater;
|
||||||
|
import org.keycloak.testsuite.util.KeyUtils;
|
||||||
import org.keycloak.testsuite.util.SamlClient;
|
import org.keycloak.testsuite.util.SamlClient;
|
||||||
import org.keycloak.testsuite.util.SamlClient.Binding;
|
import org.keycloak.testsuite.util.SamlClient.Binding;
|
||||||
import org.keycloak.testsuite.util.SamlClientBuilder;
|
import org.keycloak.testsuite.util.SamlClientBuilder;
|
||||||
|
@ -66,7 +69,7 @@ public class KcSamlSignedBrokerTest extends KcSamlBrokerTest {
|
||||||
public List<ClientRepresentation> createProviderClients(SuiteContext suiteContext) {
|
public List<ClientRepresentation> createProviderClients(SuiteContext suiteContext) {
|
||||||
List<ClientRepresentation> clientRepresentationList = super.createProviderClients(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());
|
Assert.assertThat(consumerCert, Matchers.notNullValue());
|
||||||
|
|
||||||
for (ClientRepresentation client : clientRepresentationList) {
|
for (ClientRepresentation client : clientRepresentationList) {
|
||||||
|
@ -93,7 +96,7 @@ public class KcSamlSignedBrokerTest extends KcSamlBrokerTest {
|
||||||
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) {
|
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) {
|
||||||
IdentityProviderRepresentation result = super.setUpIdentityProvider(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());
|
Assert.assertThat(providerCert, Matchers.notNullValue());
|
||||||
|
|
||||||
Map<String, String> config = result.getConfig();
|
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());
|
final ClientResource clientResource = realmsResouce().realm(bc.providerRealmName()).clients().get(client.getId());
|
||||||
Assert.assertThat(clientResource, Matchers.notNullValue());
|
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());
|
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());
|
Assert.assertThat(consumerCert, Matchers.notNullValue());
|
||||||
|
|
||||||
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
|
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
|
||||||
|
|
|
@ -24,6 +24,8 @@ import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.common.util.Base64Url;
|
import org.keycloak.common.util.Base64Url;
|
||||||
import org.keycloak.common.util.MultivaluedHashMap;
|
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.jose.jws.AlgorithmType;
|
||||||
import org.keycloak.keys.GeneratedHmacKeyProviderFactory;
|
import org.keycloak.keys.GeneratedHmacKeyProviderFactory;
|
||||||
import org.keycloak.keys.KeyProvider;
|
import org.keycloak.keys.KeyProvider;
|
||||||
|
@ -90,14 +92,14 @@ public class GeneratedHmacKeyProviderTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
KeysMetadataRepresentation.KeyMetadataRepresentation key = null;
|
KeysMetadataRepresentation.KeyMetadataRepresentation key = null;
|
||||||
for (KeysMetadataRepresentation.KeyMetadataRepresentation k : keys.getKeys()) {
|
for (KeysMetadataRepresentation.KeyMetadataRepresentation k : keys.getKeys()) {
|
||||||
if (k.getType().equals(AlgorithmType.HMAC.name())) {
|
if (k.getAlgorithms().contains(Algorithm.HS256)) {
|
||||||
key = k;
|
key = k;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals(id, key.getProviderId());
|
assertEquals(id, key.getProviderId());
|
||||||
assertEquals(AlgorithmType.HMAC.name(), key.getType());
|
assertEquals(KeyType.OCT, key.getType());
|
||||||
assertEquals(priority, key.getProviderPriority());
|
assertEquals(priority, key.getProviderPriority());
|
||||||
|
|
||||||
ComponentRepresentation component = testingClient.server("test").fetch(RunHelpers.internalComponent(id));
|
ComponentRepresentation component = testingClient.server("test").fetch(RunHelpers.internalComponent(id));
|
||||||
|
@ -125,14 +127,14 @@ public class GeneratedHmacKeyProviderTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
KeysMetadataRepresentation.KeyMetadataRepresentation key = null;
|
KeysMetadataRepresentation.KeyMetadataRepresentation key = null;
|
||||||
for (KeysMetadataRepresentation.KeyMetadataRepresentation k : keys.getKeys()) {
|
for (KeysMetadataRepresentation.KeyMetadataRepresentation k : keys.getKeys()) {
|
||||||
if (k.getType().equals(AlgorithmType.HMAC.name())) {
|
if (k.getAlgorithms().contains(Algorithm.HS256)) {
|
||||||
key = k;
|
key = k;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals(id, key.getProviderId());
|
assertEquals(id, key.getProviderId());
|
||||||
assertEquals(AlgorithmType.HMAC.name(), key.getType());
|
assertEquals(KeyType.OCT, key.getType());
|
||||||
assertEquals(priority, key.getProviderPriority());
|
assertEquals(priority, key.getProviderPriority());
|
||||||
|
|
||||||
ComponentRepresentation component = testingClient.server("test").fetch(RunHelpers.internalComponent(id));
|
ComponentRepresentation component = testingClient.server("test").fetch(RunHelpers.internalComponent(id));
|
||||||
|
|
|
@ -24,6 +24,7 @@ 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.crypto.Algorithm;
|
||||||
import org.keycloak.jose.jws.AlgorithmType;
|
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.ImportedRsaKeyProviderFactory;
|
||||||
|
@ -90,7 +91,7 @@ public class ImportedRsaKeyProviderTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
KeysMetadataRepresentation keys = adminClient.realm("test").keys().getKeyMetadata();
|
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);
|
KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0);
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.keycloak.common.VerificationException;
|
||||||
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.crypto.Algorithm;
|
||||||
import org.keycloak.keys.Attributes;
|
import org.keycloak.keys.Attributes;
|
||||||
import org.keycloak.keys.GeneratedHmacKeyProviderFactory;
|
import org.keycloak.keys.GeneratedHmacKeyProviderFactory;
|
||||||
import org.keycloak.keys.KeyProvider;
|
import org.keycloak.keys.KeyProvider;
|
||||||
|
@ -218,7 +219,7 @@ public class KeyRotationTest extends AbstractKeycloakTest {
|
||||||
PublicKey keys2 = createKeys2();
|
PublicKey keys2 = createKeys2();
|
||||||
|
|
||||||
KeysMetadataRepresentation keyMetadata = adminClient.realm("test").keys().getKeyMetadata();
|
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();
|
dropKeys1();
|
||||||
dropKeys2();
|
dropKeys2();
|
||||||
|
@ -227,7 +228,7 @@ public class KeyRotationTest extends AbstractKeycloakTest {
|
||||||
@Test
|
@Test
|
||||||
public void rotateKeys() throws InterruptedException {
|
public void rotateKeys() throws InterruptedException {
|
||||||
for (int i = 0; i < 10; i++) {
|
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
|
// Rotate public keys on the parent broker
|
||||||
String realmId = adminClient.realm("test").toRepresentation().getId();
|
String realmId = adminClient.realm("test").toRepresentation().getId();
|
||||||
|
@ -244,7 +245,7 @@ public class KeyRotationTest extends AbstractKeycloakTest {
|
||||||
getCleanup().addComponentId(newId);
|
getCleanup().addComponentId(newId);
|
||||||
response.close();
|
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);
|
assertNotEquals(activeKid, updatedActiveKid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,6 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.testsuite;
|
package org.keycloak.testsuite;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
|
|
||||||
import javax.ws.rs.WebApplicationException;
|
import javax.ws.rs.WebApplicationException;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.Response.Status;
|
import javax.ws.rs.core.Response.Status;
|
||||||
|
@ -29,8 +27,6 @@ import java.net.URI;
|
||||||
*/
|
*/
|
||||||
public class ApiUtil {
|
public class ApiUtil {
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(ApiUtil.class);
|
|
||||||
|
|
||||||
public static String getCreatedId(Response response) {
|
public static String getCreatedId(Response response) {
|
||||||
URI location = response.getLocation();
|
URI location = response.getLocation();
|
||||||
if (!response.getStatusInfo().equals(Status.CREATED)) {
|
if (!response.getStatusInfo().equals(Status.CREATED)) {
|
||||||
|
|
|
@ -1388,7 +1388,6 @@ authz-evaluation-authorization-data.tooltip=Represents a token carrying authoriz
|
||||||
authz-show-authorization-data=Show Authorization Data
|
authz-show-authorization-data=Show Authorization Data
|
||||||
|
|
||||||
keys=Keys
|
keys=Keys
|
||||||
all=All
|
|
||||||
status=Status
|
status=Status
|
||||||
keystore=Keystore
|
keystore=Keystore
|
||||||
keystores=Keystores
|
keystores=Keystores
|
||||||
|
@ -1396,6 +1395,10 @@ add-keystore=Add Keystore
|
||||||
add-keystore.placeholder=Add keystore...
|
add-keystore.placeholder=Add keystore...
|
||||||
view=View
|
view=View
|
||||||
active=Active
|
active=Active
|
||||||
|
passive=Passive
|
||||||
|
disabled=Disabled
|
||||||
|
algorithms=Algorithms
|
||||||
|
providerHelpText=Provider description
|
||||||
|
|
||||||
Sunday=Sunday
|
Sunday=Sunday
|
||||||
Monday=Monday
|
Monday=Monday
|
||||||
|
|
|
@ -289,8 +289,23 @@ module.config([ '$routeProvider', function($routeProvider) {
|
||||||
},
|
},
|
||||||
controller : 'RealmKeysCtrl'
|
controller : 'RealmKeysCtrl'
|
||||||
})
|
})
|
||||||
.when('/realms/:realm/keys/list', {
|
.when('/realms/:realm/keys/passive', {
|
||||||
templateUrl : resourceUrl + '/partials/realm-keys-list.html',
|
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 : {
|
resolve : {
|
||||||
realm : function(RealmLoader) {
|
realm : function(RealmLoader) {
|
||||||
return RealmLoader();
|
return RealmLoader();
|
||||||
|
|
|
@ -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>
|
|
@ -20,15 +20,30 @@
|
||||||
|
|
||||||
<ul class="nav nav-tabs nav-tabs-pf">
|
<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">{{:: '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>
|
<li><a href="#/realms/{{realm.realm}}/keys/providers">{{:: 'providers' | translate}}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<table class="table table-striped table-bordered">
|
<table class="table table-striped table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<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>{{:: '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>
|
||||||
|
@ -36,9 +51,9 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<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.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>
|
|
@ -20,16 +20,26 @@
|
||||||
|
|
||||||
<ul class="nav nav-tabs nav-tabs-pf">
|
<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">{{:: '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>
|
<li class="active"><a href="#/realms/{{realm.realm}}/keys/providers">{{:: 'providers' | translate}}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<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>
|
||||||
<th colspan="7" class="kc-table-actions">
|
<th class="kc-table-actions" colspan="7">
|
||||||
<div class="pull-right">
|
<div class="form-inline">
|
||||||
<div>
|
<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"
|
<select class="form-control" ng-model="selectedProvider"
|
||||||
ng-options="p.id for p in providers"
|
ng-options="p.id for p in providers"
|
||||||
data-ng-change="addProvider(selectedProvider); selectedProvider = null">
|
data-ng-change="addProvider(selectedProvider); selectedProvider = null">
|
||||||
|
@ -40,20 +50,18 @@
|
||||||
</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>{{:: 'provider' | translate}}</th>
|
<th>{{:: 'provider' | translate}}</th>
|
||||||
|
<th>{{:: 'providerHelpText' | translate}}</th>
|
||||||
<th>{{:: 'priority' | translate}}</th>
|
<th>{{:: 'priority' | translate}}</th>
|
||||||
<th colspan="2">{{:: 'actions' | translate}}</th>
|
<th colspan="2">{{:: 'actions' | translate}}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr ng-repeat="instance in instances">
|
<tr ng-repeat="instance in instances | filter:search">
|
||||||
<td>{{instance.provider.metadata.algorithmType}}</td>
|
<td><a href="#/realms/{{realm.realm}}/keys/providers/{{instance.providerId}}/{{instance.id}}">{{instance.name}}</a></td>
|
||||||
<td>{{instance.name}}</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>
|
||||||
|
<td>{{instance.provider.helpText}}</td>
|
||||||
<td>{{instance.config['priority'][0]}}</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" 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>
|
<td class="kc-action-cell" ng-show="instances.length > 1" data-ng-click="removeInstance(instance)">{{:: 'delete' | translate}}</td>
|
||||||
|
|
|
@ -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">
|
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||||
<kc-tabs-realm></kc-tabs-realm>
|
<kc-tabs-realm></kc-tabs-realm>
|
||||||
|
|
||||||
<ul class="nav nav-tabs nav-tabs-pf">
|
<ul class="nav nav-tabs nav-tabs-pf">
|
||||||
<li class="active"><a href="#/realms/{{realm.realm}}/keys">{{:: 'active' | translate}}</a></li>
|
<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>
|
<li><a href="#/realms/{{realm.realm}}/keys/providers">{{:: 'providers' | translate}}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<table class="table table-striped table-bordered">
|
<table class="table table-striped table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<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>{{:: 'type' | translate}}</th>
|
||||||
<th>{{:: 'kid' | translate}}</th>
|
<th>{{:: 'kid' | translate}}</th>
|
||||||
|
<th>{{:: 'priority' | translate}}</th>
|
||||||
<th>{{:: 'provider' | translate}}</th>
|
<th>{{:: 'provider' | translate}}</th>
|
||||||
<th colspan="2">{{:: 'publicKeys' | translate}}</th>
|
<th colspan="2">{{:: 'publicKeys' | translate}}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<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.type}}</td>
|
||||||
<td>{{key.kid}}</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><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.publicKey)">{{:: 'publicKey' | translate}}</td>
|
||||||
|
|
Loading…
Reference in a new issue