Merge pull request #3665 from stianst/KEYCLOAK-4092
KEYCLOAK-4092 key provider for HMAC signatures
This commit is contained in:
commit
ca3691e650
65 changed files with 1578 additions and 329 deletions
|
@ -17,6 +17,8 @@
|
|||
|
||||
package org.keycloak.common.util;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.Key;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
|
@ -38,6 +40,10 @@ public class KeyUtils {
|
|||
private KeyUtils() {
|
||||
}
|
||||
|
||||
public static SecretKey loadSecretKey(String secret) {
|
||||
return new SecretKeySpec(secret.getBytes(), "HmacSHA256");
|
||||
}
|
||||
|
||||
public static KeyPair generateRsaKeyPair(int keysize) {
|
||||
try {
|
||||
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
|
||||
|
|
|
@ -19,11 +19,7 @@ package org.keycloak;
|
|||
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.jose.jws.JWSHeader;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.JWSInputException;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
|
@ -33,18 +29,10 @@ import java.security.PublicKey;
|
|||
*/
|
||||
public class RSATokenVerifier {
|
||||
|
||||
private final String tokenString;
|
||||
private PublicKey publicKey;
|
||||
private String realmUrl;
|
||||
private boolean checkTokenType = true;
|
||||
private boolean checkActive = true;
|
||||
private boolean checkRealmUrl = true;
|
||||
|
||||
private JWSInput jws;
|
||||
private AccessToken token;
|
||||
private TokenVerifier tokenVerifier;
|
||||
|
||||
private RSATokenVerifier(String tokenString) {
|
||||
this.tokenString = tokenString;
|
||||
this.tokenVerifier = TokenVerifier.create(tokenString);
|
||||
}
|
||||
|
||||
public static RSATokenVerifier create(String tokenString) {
|
||||
|
@ -60,94 +48,45 @@ public class RSATokenVerifier {
|
|||
}
|
||||
|
||||
public RSATokenVerifier publicKey(PublicKey publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
tokenVerifier.publicKey(publicKey);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RSATokenVerifier realmUrl(String realmUrl) {
|
||||
this.realmUrl = realmUrl;
|
||||
tokenVerifier.realmUrl(realmUrl);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RSATokenVerifier checkTokenType(boolean checkTokenType) {
|
||||
this.checkTokenType = checkTokenType;
|
||||
tokenVerifier.checkTokenType(checkTokenType);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RSATokenVerifier checkActive(boolean checkActive) {
|
||||
this.checkActive = checkActive;
|
||||
tokenVerifier.checkActive(checkActive);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RSATokenVerifier checkRealmUrl(boolean checkRealmUrl) {
|
||||
this.checkRealmUrl = checkRealmUrl;
|
||||
tokenVerifier.checkRealmUrl(checkRealmUrl);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RSATokenVerifier parse() throws VerificationException {
|
||||
if (jws == null) {
|
||||
if (tokenString == null) {
|
||||
throw new VerificationException("Token not set");
|
||||
}
|
||||
|
||||
try {
|
||||
jws = new JWSInput(tokenString);
|
||||
} catch (JWSInputException e) {
|
||||
throw new VerificationException("Failed to parse JWT", e);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
token = jws.readJsonContent(AccessToken.class);
|
||||
} catch (JWSInputException e) {
|
||||
throw new VerificationException("Failed to read access token from JWT", e);
|
||||
}
|
||||
}
|
||||
tokenVerifier.parse();
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccessToken getToken() throws VerificationException {
|
||||
parse();
|
||||
return token;
|
||||
return tokenVerifier.getToken();
|
||||
}
|
||||
|
||||
public JWSHeader getHeader() throws VerificationException {
|
||||
parse();
|
||||
return jws.getHeader();
|
||||
return tokenVerifier.getHeader();
|
||||
}
|
||||
|
||||
public RSATokenVerifier verify() throws VerificationException {
|
||||
parse();
|
||||
|
||||
if (publicKey == null) {
|
||||
throw new VerificationException("Public key not set");
|
||||
}
|
||||
|
||||
if (checkRealmUrl && realmUrl == null) {
|
||||
throw new VerificationException("Realm URL not set");
|
||||
}
|
||||
|
||||
if (!RSAProvider.verify(jws, publicKey)) {
|
||||
throw new VerificationException("Invalid token signature");
|
||||
}
|
||||
|
||||
String user = token.getSubject();
|
||||
if (user == null) {
|
||||
throw new VerificationException("Subject missing in token");
|
||||
}
|
||||
|
||||
if (checkRealmUrl && !realmUrl.equals(token.getIssuer())) {
|
||||
throw new VerificationException("Invalid token issuer. Expected '" + realmUrl + "', but was '" + token.getIssuer() + "'");
|
||||
}
|
||||
|
||||
if (checkTokenType && !TokenUtil.TOKEN_TYPE_BEARER.equalsIgnoreCase(token.getType())) {
|
||||
throw new VerificationException("Token type is incorrect. Expected '" + TokenUtil.TOKEN_TYPE_BEARER + "' but was '" + token.getType() + "'");
|
||||
}
|
||||
|
||||
if (checkActive && !token.isActive()) {
|
||||
throw new VerificationException("Token is not active");
|
||||
}
|
||||
|
||||
tokenVerifier.verify();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
170
core/src/main/java/org/keycloak/TokenVerifier.java
Executable file
170
core/src/main/java/org/keycloak/TokenVerifier.java
Executable file
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak;
|
||||
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.jose.jws.Algorithm;
|
||||
import org.keycloak.jose.jws.AlgorithmType;
|
||||
import org.keycloak.jose.jws.JWSHeader;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.JWSInputException;
|
||||
import org.keycloak.jose.jws.crypto.HMACProvider;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class TokenVerifier {
|
||||
|
||||
private final String tokenString;
|
||||
private PublicKey publicKey;
|
||||
private SecretKey secretKey;
|
||||
private String realmUrl;
|
||||
private boolean checkTokenType = true;
|
||||
private boolean checkActive = true;
|
||||
private boolean checkRealmUrl = true;
|
||||
|
||||
private JWSInput jws;
|
||||
private AccessToken token;
|
||||
|
||||
protected TokenVerifier(String tokenString) {
|
||||
this.tokenString = tokenString;
|
||||
}
|
||||
|
||||
public static TokenVerifier create(String tokenString) {
|
||||
return new TokenVerifier(tokenString);
|
||||
}
|
||||
|
||||
public TokenVerifier publicKey(PublicKey publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TokenVerifier secretKey(SecretKey secretKey) {
|
||||
this.secretKey = secretKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TokenVerifier realmUrl(String realmUrl) {
|
||||
this.realmUrl = realmUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TokenVerifier checkTokenType(boolean checkTokenType) {
|
||||
this.checkTokenType = checkTokenType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TokenVerifier checkActive(boolean checkActive) {
|
||||
this.checkActive = checkActive;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TokenVerifier checkRealmUrl(boolean checkRealmUrl) {
|
||||
this.checkRealmUrl = checkRealmUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TokenVerifier parse() throws VerificationException {
|
||||
if (jws == null) {
|
||||
if (tokenString == null) {
|
||||
throw new VerificationException("Token not set");
|
||||
}
|
||||
|
||||
try {
|
||||
jws = new JWSInput(tokenString);
|
||||
} catch (JWSInputException e) {
|
||||
throw new VerificationException("Failed to parse JWT", e);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
token = jws.readJsonContent(AccessToken.class);
|
||||
} catch (JWSInputException e) {
|
||||
throw new VerificationException("Failed to read access token from JWT", e);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccessToken getToken() throws VerificationException {
|
||||
parse();
|
||||
return token;
|
||||
}
|
||||
|
||||
public JWSHeader getHeader() throws VerificationException {
|
||||
parse();
|
||||
return jws.getHeader();
|
||||
}
|
||||
|
||||
public TokenVerifier verify() throws VerificationException {
|
||||
parse();
|
||||
|
||||
if (checkRealmUrl && realmUrl == null) {
|
||||
throw new VerificationException("Realm URL not set");
|
||||
}
|
||||
|
||||
AlgorithmType algorithmType = getHeader().getAlgorithm().getType();
|
||||
|
||||
if (AlgorithmType.RSA.equals(algorithmType)) {
|
||||
if (publicKey == null) {
|
||||
throw new VerificationException("Public key not set");
|
||||
}
|
||||
|
||||
if (!RSAProvider.verify(jws, publicKey)) {
|
||||
throw new VerificationException("Invalid token signature");
|
||||
}
|
||||
} else if (AlgorithmType.HMAC.equals(algorithmType)) {
|
||||
if (secretKey == null) {
|
||||
throw new VerificationException("Secret key not set");
|
||||
}
|
||||
|
||||
if (!HMACProvider.verify(jws, secretKey)) {
|
||||
throw new VerificationException("Invalid token signature");
|
||||
}
|
||||
} else {
|
||||
throw new VerificationException("Unknown or unsupported token algorith");
|
||||
}
|
||||
|
||||
String user = token.getSubject();
|
||||
if (user == null) {
|
||||
throw new VerificationException("Subject missing in token");
|
||||
}
|
||||
|
||||
if (checkRealmUrl && !realmUrl.equals(token.getIssuer())) {
|
||||
throw new VerificationException("Invalid token issuer. Expected '" + realmUrl + "', but was '" + token.getIssuer() + "'");
|
||||
}
|
||||
|
||||
if (checkTokenType && !TokenUtil.TOKEN_TYPE_BEARER.equalsIgnoreCase(token.getType())) {
|
||||
throw new VerificationException("Token type is incorrect. Expected '" + TokenUtil.TOKEN_TYPE_BEARER + "' but was '" + token.getType() + "'");
|
||||
}
|
||||
|
||||
if (checkActive && !token.isActive()) {
|
||||
throw new VerificationException("Token is not active");
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -26,23 +26,30 @@ import org.keycloak.jose.jws.crypto.SignatureProvider;
|
|||
*/
|
||||
public enum Algorithm {
|
||||
|
||||
none(null),
|
||||
HS256(null),
|
||||
HS384(null),
|
||||
HS512(null),
|
||||
RS256(new RSAProvider()),
|
||||
RS384(new RSAProvider()),
|
||||
RS512(new RSAProvider()),
|
||||
ES256(null),
|
||||
ES384(null),
|
||||
ES512(null)
|
||||
none(null, null),
|
||||
HS256(AlgorithmType.HMAC, null),
|
||||
HS384(AlgorithmType.HMAC, null),
|
||||
HS512(AlgorithmType.HMAC, null),
|
||||
RS256(AlgorithmType.RSA, new RSAProvider()),
|
||||
RS384(AlgorithmType.RSA, new RSAProvider()),
|
||||
RS512(AlgorithmType.RSA, new RSAProvider()),
|
||||
ES256(AlgorithmType.ECDSA, null),
|
||||
ES384(AlgorithmType.ECDSA, null),
|
||||
ES512(AlgorithmType.ECDSA, null)
|
||||
;
|
||||
|
||||
private AlgorithmType type;
|
||||
private SignatureProvider provider;
|
||||
|
||||
Algorithm(SignatureProvider provider) {
|
||||
Algorithm(AlgorithmType type, SignatureProvider provider) {
|
||||
this.type = type;
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public AlgorithmType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public SignatureProvider getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
|
30
core/src/main/java/org/keycloak/jose/jws/AlgorithmType.java
Executable file
30
core/src/main/java/org/keycloak/jose/jws/AlgorithmType.java
Executable file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.jose.jws;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public enum AlgorithmType {
|
||||
|
||||
RSA,
|
||||
HMAC,
|
||||
ECDSA
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.keys;
|
||||
|
||||
import org.keycloak.jose.jws.AlgorithmType;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface HmacKeyProvider extends KeyProvider<HmacKeyMetadata> {
|
||||
|
||||
default AlgorithmType getType() {
|
||||
return AlgorithmType.HMAC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the active secret key, or <code>null</code> if no active key is available.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
SecretKey getSecretKey();
|
||||
|
||||
/**
|
||||
* Return the secret key for the specified kid, or <code>null</code> if the kid is unknown.
|
||||
*
|
||||
* @param kid
|
||||
* @return
|
||||
*/
|
||||
SecretKey getSecretKey(String kid);
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.keys;
|
||||
|
||||
import org.keycloak.jose.jws.AlgorithmType;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface HmacKeyProviderFactory extends KeyProviderFactory {
|
||||
|
||||
@Override
|
||||
default Map<String, Object> getTypeMetadata() {
|
||||
return Collections.singletonMap("algorithmType", AlgorithmType.HMAC);
|
||||
}
|
||||
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.keycloak.keys;
|
||||
|
||||
import org.keycloak.jose.jws.AlgorithmType;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
|
@ -27,7 +28,14 @@ import java.util.List;
|
|||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface KeyProvider extends Provider {
|
||||
public interface KeyProvider<T extends KeyMetadata> extends Provider {
|
||||
|
||||
/**
|
||||
* Returns the algorithm type the keys can be used for
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
AlgorithmType getType();
|
||||
|
||||
/**
|
||||
* Return the KID for the active keypair, or <code>null</code> if no active key is available.
|
||||
|
@ -36,33 +44,10 @@ public interface KeyProvider extends Provider {
|
|||
*/
|
||||
String getKid();
|
||||
|
||||
/**
|
||||
* Return the private key for the active keypair, or <code>null</code> if no active key is available.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
PrivateKey getPrivateKey();
|
||||
|
||||
/**
|
||||
* Return the public key for the specified kid, or <code>null</code> if the kid is unknown.
|
||||
*
|
||||
* @param kid
|
||||
* @return
|
||||
*/
|
||||
PublicKey getPublicKey(String kid);
|
||||
|
||||
/**
|
||||
* Return the certificate for the specified kid, or <code>null</code> if the kid is unknown.
|
||||
*
|
||||
* @param kid
|
||||
* @return
|
||||
*/
|
||||
X509Certificate getCertificate(String kid);
|
||||
|
||||
/**
|
||||
* Return metadata about all keypairs held by the provider
|
||||
* @return
|
||||
*/
|
||||
List<KeyMetadata> getKeyMetadata();
|
||||
List<T> getKeyMetadata();
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.keys;
|
||||
|
||||
import org.keycloak.jose.jws.AlgorithmType;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface RsaKeyProvider extends KeyProvider<RsaKeyMetadata> {
|
||||
|
||||
default AlgorithmType getType() {
|
||||
return AlgorithmType.RSA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the private key for the active keypair, or <code>null</code> if no active key is available.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
PrivateKey getPrivateKey();
|
||||
|
||||
/**
|
||||
* Return the public key for the specified kid, or <code>null</code> if the kid is unknown.
|
||||
*
|
||||
* @param kid
|
||||
* @return
|
||||
*/
|
||||
PublicKey getPublicKey(String kid);
|
||||
|
||||
/**
|
||||
* Return the certificate for the specified kid, or <code>null</code> if the kid is unknown.
|
||||
*
|
||||
* @param kid
|
||||
* @return
|
||||
*/
|
||||
X509Certificate getCertificate(String kid);
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.keys;
|
||||
|
||||
import org.keycloak.jose.jws.AlgorithmType;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface RsaKeyProviderFactory extends KeyProviderFactory {
|
||||
|
||||
@Override
|
||||
default Map<String, Object> getTypeMetadata() {
|
||||
return Collections.singletonMap("algorithmType", AlgorithmType.RSA);
|
||||
}
|
||||
|
||||
}
|
|
@ -31,6 +31,7 @@ import org.keycloak.migration.migrators.MigrateTo2_0_0;
|
|||
import org.keycloak.migration.migrators.MigrateTo2_1_0;
|
||||
import org.keycloak.migration.migrators.MigrateTo2_2_0;
|
||||
import org.keycloak.migration.migrators.MigrateTo2_3_0;
|
||||
import org.keycloak.migration.migrators.MigrateTo2_5_0;
|
||||
import org.keycloak.migration.migrators.Migration;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
|
@ -55,6 +56,7 @@ public class MigrationModelManager {
|
|||
new MigrateTo2_1_0(),
|
||||
new MigrateTo2_2_0(),
|
||||
new MigrateTo2_3_0(),
|
||||
new MigrateTo2_5_0(),
|
||||
};
|
||||
|
||||
public static void migrate(KeycloakSession session) {
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.migration.migrators;
|
||||
|
||||
|
||||
import org.keycloak.migration.ModelVersion;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientTemplateModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.DefaultKeyProviders;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class MigrateTo2_5_0 implements Migration {
|
||||
|
||||
public static final ModelVersion VERSION = new ModelVersion("2.5.0");
|
||||
|
||||
@Override
|
||||
public void migrate(KeycloakSession session) {
|
||||
session.realms().getRealms().stream().forEach(
|
||||
r -> DefaultKeyProviders.createSecretProvider(r)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelVersion getVersion() {
|
||||
return VERSION;
|
||||
}
|
||||
}
|
|
@ -39,6 +39,22 @@ public class DefaultKeyProviders {
|
|||
generated.setConfig(config);
|
||||
|
||||
realm.addComponentModel(generated);
|
||||
|
||||
createSecretProvider(realm);
|
||||
}
|
||||
|
||||
public static void createSecretProvider(RealmModel realm) {
|
||||
ComponentModel generated = new ComponentModel();
|
||||
generated.setName("hmac-generated");
|
||||
generated.setParentId(realm.getId());
|
||||
generated.setProviderId("hmac-generated");
|
||||
generated.setProviderType(KeyProvider.class.getName());
|
||||
|
||||
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
|
||||
config.putSingle("priority", "100");
|
||||
generated.setConfig(config);
|
||||
|
||||
realm.addComponentModel(generated);
|
||||
}
|
||||
|
||||
public static void createProviders(RealmModel realm, String privateKeyPem, String certificatePem) {
|
||||
|
@ -57,6 +73,8 @@ public class DefaultKeyProviders {
|
|||
rsa.setConfig(config);
|
||||
|
||||
realm.addComponentModel(rsa);
|
||||
|
||||
createSecretProvider(realm);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -41,6 +41,28 @@ public class ConfigurationValidationHelper {
|
|||
return checkInt(property.getName(), property.getLabel(), required);
|
||||
}
|
||||
|
||||
public ConfigurationValidationHelper checkList(ProviderConfigProperty property, boolean required) throws ComponentValidationException {
|
||||
checkSingle(property.getName(), property.getLabel(), required);
|
||||
|
||||
String value = model.getConfig().getFirst(property.getName());
|
||||
if (value != null && !property.options.contains(value)) {
|
||||
StringBuilder options = new StringBuilder();
|
||||
int i = 1;
|
||||
for (String o : property.options) {
|
||||
if (i == property.options.size()) {
|
||||
options.append(" or ");
|
||||
} else if (i > 1) {
|
||||
options.append(", ");
|
||||
}
|
||||
options.append(o);
|
||||
i++;
|
||||
}
|
||||
throw new ComponentValidationException("''{0}'' should be {1}", property.label, options.toString());
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConfigurationValidationHelper checkInt(String key, String label, boolean required) throws ComponentValidationException {
|
||||
checkSingle(key, label, required);
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.keys;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class HmacKeyMetadata extends KeyMetadata {
|
||||
|
||||
}
|
|
@ -17,22 +17,15 @@
|
|||
|
||||
package org.keycloak.keys;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.Certificate;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class KeyMetadata {
|
||||
public abstract class KeyMetadata {
|
||||
|
||||
public enum Status {
|
||||
ACTIVE, PASSIVE, DISABLED
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
RSA
|
||||
}
|
||||
|
||||
private String providerId;
|
||||
private long providerPriority;
|
||||
|
||||
|
@ -40,11 +33,6 @@ public class KeyMetadata {
|
|||
|
||||
private Status status;
|
||||
|
||||
private Type type;
|
||||
|
||||
private PublicKey publicKey;
|
||||
private Certificate certificate;
|
||||
|
||||
public String getProviderId() {
|
||||
return providerId;
|
||||
}
|
||||
|
@ -77,28 +65,4 @@ public class KeyMetadata {
|
|||
this.status = status;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public PublicKey getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public void setPublicKey(PublicKey publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
public Certificate getCertificate() {
|
||||
return certificate;
|
||||
}
|
||||
|
||||
public void setCertificate(Certificate certificate) {
|
||||
this.certificate = certificate;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.keys;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.Certificate;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class RsaKeyMetadata extends KeyMetadata {
|
||||
|
||||
private PublicKey publicKey;
|
||||
private Certificate certificate;
|
||||
|
||||
public PublicKey getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public void setPublicKey(PublicKey publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
public Certificate getCertificate() {
|
||||
return certificate;
|
||||
}
|
||||
|
||||
public void setCertificate(Certificate certificate) {
|
||||
this.certificate = certificate;
|
||||
}
|
||||
|
||||
}
|
|
@ -17,8 +17,10 @@
|
|||
|
||||
package org.keycloak.models;
|
||||
|
||||
import org.keycloak.keys.KeyMetadata;
|
||||
import org.keycloak.keys.HmacKeyMetadata;
|
||||
import org.keycloak.keys.RsaKeyMetadata;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.Certificate;
|
||||
|
@ -30,21 +32,27 @@ import java.util.List;
|
|||
*/
|
||||
public interface KeyManager {
|
||||
|
||||
ActiveKey getActiveKey(RealmModel realm);
|
||||
ActiveRsaKey getActiveRsaKey(RealmModel realm);
|
||||
|
||||
PublicKey getPublicKey(RealmModel realm, String kid);
|
||||
PublicKey getRsaPublicKey(RealmModel realm, String kid);
|
||||
|
||||
Certificate getCertificate(RealmModel realm, String kid);
|
||||
Certificate getRsaCertificate(RealmModel realm, String kid);
|
||||
|
||||
List<KeyMetadata> getKeys(RealmModel realm, boolean includeDisabled);
|
||||
List<RsaKeyMetadata> getRsaKeys(RealmModel realm, boolean includeDisabled);
|
||||
|
||||
class ActiveKey {
|
||||
ActiveHmacKey getActiveHmacKey(RealmModel realm);
|
||||
|
||||
SecretKey getHmacSecretKey(RealmModel realm, String kid);
|
||||
|
||||
List<HmacKeyMetadata> getHmacKeys(RealmModel realm, boolean includeDisabled);
|
||||
|
||||
class ActiveRsaKey {
|
||||
private final String kid;
|
||||
private final PrivateKey privateKey;
|
||||
private final PublicKey publicKey;
|
||||
private final X509Certificate certificate;
|
||||
|
||||
public ActiveKey(String kid, PrivateKey privateKey, PublicKey publicKey, X509Certificate certificate) {
|
||||
public ActiveRsaKey(String kid, PrivateKey privateKey, PublicKey publicKey, X509Certificate certificate) {
|
||||
this.kid = kid;
|
||||
this.privateKey = privateKey;
|
||||
this.publicKey = publicKey;
|
||||
|
@ -68,4 +76,22 @@ public interface KeyManager {
|
|||
}
|
||||
}
|
||||
|
||||
class ActiveHmacKey {
|
||||
private final String kid;
|
||||
private final SecretKey secretKey;
|
||||
|
||||
public ActiveHmacKey(String kid, SecretKey secretKey) {
|
||||
this.kid = kid;
|
||||
this.secretKey = secretKey;
|
||||
}
|
||||
|
||||
public String getKid() {
|
||||
return kid;
|
||||
}
|
||||
|
||||
public SecretKey getSecretKey() {
|
||||
return secretKey;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,6 +22,6 @@ package org.keycloak.provider;
|
|||
*/
|
||||
public interface Provider {
|
||||
|
||||
public void close();
|
||||
void close();
|
||||
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ public class UmaWellKnownProvider implements WellKnownProvider {
|
|||
return Configuration.fromDefault(RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString(), realm.getName(),
|
||||
URI.create(RealmsResource.protocolUrl(uriInfo).path(OIDCLoginProtocolService.class, "auth").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString()),
|
||||
URI.create(RealmsResource.protocolUrl(uriInfo).path(OIDCLoginProtocolService.class, "token").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString()),
|
||||
PemUtils.encodeKey(session.keys().getActiveKey(realm).getPublicKey()));
|
||||
PemUtils.encodeKey(session.keys().getActiveRsaKey(realm).getPublicKey()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -131,7 +131,7 @@ public class AbstractPermissionService {
|
|||
}
|
||||
|
||||
private String createPermissionTicket(List<ResourceRepresentation> resources) {
|
||||
KeyManager.ActiveKey keys = this.authorization.getKeycloakSession().keys().getActiveKey(this.authorization.getRealm());
|
||||
KeyManager.ActiveRsaKey keys = this.authorization.getKeycloakSession().keys().getActiveRsaKey(this.authorization.getRealm());
|
||||
return new JWSBuilder().kid(keys.getKid()).jsonContent(new PermissionTicket(resources, this.resourceServer.getId(), this.identity.getAccessToken()))
|
||||
.rsa256(keys.getPrivateKey());
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ public class Tokens {
|
|||
public static boolean verifySignature(KeycloakSession keycloakSession, RealmModel realm, String token) {
|
||||
try {
|
||||
JWSInput jws = new JWSInput(token);
|
||||
PublicKey publicKey = keycloakSession.keys().getPublicKey(realm, jws.getHeader().getKeyId());
|
||||
PublicKey publicKey = keycloakSession.keys().getRsaPublicKey(realm, jws.getHeader().getKeyId());
|
||||
return RSAProvider.verify(jws, publicKey);
|
||||
} catch (Exception e) {
|
||||
throw new ErrorResponseException("invalid_signature", "Unexpected error while validating signature.", Status.INTERNAL_SERVER_ERROR);
|
||||
|
|
|
@ -301,7 +301,7 @@ public class SAMLEndpoint {
|
|||
.relayState(relayState);
|
||||
boolean postBinding = config.isPostBindingResponse();
|
||||
if (config.isWantAuthnRequestsSigned()) {
|
||||
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
|
||||
KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
|
||||
String keyName = config.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate());
|
||||
binding.signWith(keyName, keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate())
|
||||
.signatureAlgorithm(provider.getSignatureAlgorithm())
|
||||
|
@ -332,7 +332,7 @@ public class SAMLEndpoint {
|
|||
protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState, String clientId) {
|
||||
|
||||
try {
|
||||
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
|
||||
KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
|
||||
AssertionType assertion = AssertionUtil.getAssertion(responseType, keys.getPrivateKey());
|
||||
SubjectType subject = assertion.getSubject();
|
||||
SubjectType.STSubType subType = subject.getSubType();
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.keycloak.dom.saml.v2.assertion.NameIDType;
|
|||
import org.keycloak.dom.saml.v2.assertion.SubjectType;
|
||||
import org.keycloak.dom.saml.v2.protocol.ResponseType;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.keys.RsaKeyMetadata;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.KeyManager;
|
||||
|
@ -103,7 +104,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||
boolean postBinding = getConfig().isPostBindingAuthnRequest();
|
||||
|
||||
if (getConfig().isWantAuthnRequestsSigned()) {
|
||||
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
|
||||
KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
|
||||
|
||||
KeyPair keypair = new KeyPair(keys.getPublicKey(), keys.getPrivateKey());
|
||||
|
||||
|
@ -205,7 +206,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
|
||||
.relayState(userSession.getId());
|
||||
if (getConfig().isWantAuthnRequestsSigned()) {
|
||||
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
|
||||
KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
|
||||
String keyName = getConfig().getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate());
|
||||
binding.signWith(keyName, keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate())
|
||||
.signatureAlgorithm(getSignatureAlgorithm())
|
||||
|
@ -236,18 +237,18 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
|
||||
|
||||
StringBuilder keysString = new StringBuilder();
|
||||
Set<KeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list
|
||||
Set<RsaKeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list
|
||||
? (int) (o2.getProviderPriority() - o1.getProviderPriority())
|
||||
: (o1.getStatus() == KeyMetadata.Status.PASSIVE ? 1 : -1));
|
||||
keys.addAll(session.keys().getKeys(realm, false));
|
||||
for (KeyMetadata key : keys) {
|
||||
keys.addAll(session.keys().getRsaKeys(realm, false));
|
||||
for (RsaKeyMetadata key : keys) {
|
||||
addKeyInfo(keysString, key, KeyTypes.SIGNING.value());
|
||||
}
|
||||
String descriptor = SPMetadataDescriptor.getSPDescriptor(authnBinding, endpoint, endpoint, wantAuthnRequestsSigned, entityId, nameIDPolicyFormat, keysString.toString());
|
||||
return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
|
||||
}
|
||||
|
||||
private static void addKeyInfo(StringBuilder target, KeyMetadata key, String purpose) {
|
||||
private static void addKeyInfo(StringBuilder target, RsaKeyMetadata key, String purpose) {
|
||||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.keys;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.component.ComponentValidationException;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.provider.ConfigurationValidationHelper;
|
||||
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public abstract class AbstractHmacKeyProviderFactory implements HmacKeyProviderFactory {
|
||||
|
||||
public final static ProviderConfigurationBuilder configurationBuilder() {
|
||||
return ProviderConfigurationBuilder.create()
|
||||
.property(Attributes.PRIORITY_PROPERTY)
|
||||
.property(Attributes.ENABLED_PROPERTY)
|
||||
.property(Attributes.ACTIVE_PROPERTY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException {
|
||||
ConfigurationValidationHelper.check(model)
|
||||
.checkLong(Attributes.PRIORITY_PROPERTY, false)
|
||||
.checkBoolean(Attributes.ENABLED_PROPERTY, false)
|
||||
.checkBoolean(Attributes.ACTIVE_PROPERTY, false);
|
||||
}
|
||||
|
||||
}
|
|
@ -18,6 +18,7 @@
|
|||
package org.keycloak.keys;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.jose.jws.AlgorithmType;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
||||
import java.security.KeyPair;
|
||||
|
@ -30,7 +31,7 @@ import java.util.List;
|
|||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public abstract class AbstractRsaKeyProvider implements KeyProvider {
|
||||
public abstract class AbstractRsaKeyProvider implements RsaKeyProvider {
|
||||
|
||||
private final boolean enabled;
|
||||
|
||||
|
@ -77,11 +78,11 @@ public abstract class AbstractRsaKeyProvider implements KeyProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final List<KeyMetadata> getKeyMetadata() {
|
||||
public final List<RsaKeyMetadata> getKeyMetadata() {
|
||||
String kid = keys.getKid();
|
||||
PublicKey publicKey = keys.getKeyPair().getPublic();
|
||||
if (kid != null && publicKey != null) {
|
||||
KeyMetadata k = new KeyMetadata();
|
||||
RsaKeyMetadata k = new RsaKeyMetadata();
|
||||
k.setProviderId(model.getId());
|
||||
k.setProviderPriority(model.get(Attributes.PRIORITY_KEY, 0l));
|
||||
k.setKid(kid);
|
||||
|
@ -92,7 +93,6 @@ public abstract class AbstractRsaKeyProvider implements KeyProvider {
|
|||
} else {
|
||||
k.setStatus(KeyMetadata.Status.DISABLED);
|
||||
}
|
||||
k.setType(KeyMetadata.Type.RSA);
|
||||
k.setPublicKey(publicKey);
|
||||
k.setCertificate(keys.getCertificate());
|
||||
return Collections.singletonList(k);
|
||||
|
|
|
@ -27,7 +27,7 @@ import org.keycloak.provider.ProviderConfigurationBuilder;
|
|||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public abstract class AbstractRsaKeyProviderFactory implements KeyProviderFactory {
|
||||
public abstract class AbstractRsaKeyProviderFactory implements RsaKeyProviderFactory {
|
||||
|
||||
public final static ProviderConfigurationBuilder configurationBuilder() {
|
||||
return ProviderConfigurationBuilder.create()
|
||||
|
|
|
@ -44,6 +44,13 @@ public interface Attributes {
|
|||
ProviderConfigProperty CERTIFICATE_PROPERTY = new ProviderConfigProperty(CERTIFICATE_KEY, "X509 Certificate", "X509 Certificate encoded in PEM format", FILE_TYPE, null);
|
||||
|
||||
String KEY_SIZE_KEY = "keySize";
|
||||
ProviderConfigProperty KEY_SIZE_PROPERTY = new ProviderConfigProperty(KEY_SIZE_KEY, "Keysize", "Size for the generated keys (1024, 2048 or 4096)", LIST_TYPE, "2048", "1024", "2048", "4096");
|
||||
ProviderConfigProperty KEY_SIZE_PROPERTY = new ProviderConfigProperty(KEY_SIZE_KEY, "Key size", "Size for the generated keys", LIST_TYPE, "2048", "1024", "2048", "4096");
|
||||
|
||||
String KID_KEY = "kid";
|
||||
|
||||
String SECRET_KEY = "secret";
|
||||
|
||||
String SECRET_SIZE_KEY = "secretSize";
|
||||
ProviderConfigProperty SECRET_SIZE_PROPERTY = new ProviderConfigProperty(SECRET_SIZE_KEY, "Secret size", "Size in bytes for the generated secret", LIST_TYPE, "32", "32", "64", "128", "256", "512");
|
||||
|
||||
}
|
||||
|
|
|
@ -19,11 +19,13 @@ package org.keycloak.keys;
|
|||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.jose.jws.AlgorithmType;
|
||||
import org.keycloak.models.KeyManager;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.Comparator;
|
||||
|
@ -47,33 +49,56 @@ public class DefaultKeyManager implements KeyManager {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ActiveKey getActiveKey(RealmModel realm) {
|
||||
public ActiveRsaKey getActiveRsaKey(RealmModel realm) {
|
||||
for (KeyProvider p : getProviders(realm)) {
|
||||
if (p.getKid() != null && p.getPrivateKey() != null) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracev("Active key realm={0} kid={1}", realm.getName(), p.getKid());
|
||||
if (p.getType().equals(AlgorithmType.RSA)) {
|
||||
RsaKeyProvider r = (RsaKeyProvider) p;
|
||||
if (r.getKid() != null && r.getPrivateKey() != null) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracev("Active key realm={0} kid={1}", realm.getName(), p.getKid());
|
||||
}
|
||||
String kid = p.getKid();
|
||||
return new ActiveRsaKey(kid, r.getPrivateKey(), r.getPublicKey(kid), r.getCertificate(kid));
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Failed to get RSA keys");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActiveHmacKey getActiveHmacKey(RealmModel realm) {
|
||||
for (KeyProvider p : getProviders(realm)) {
|
||||
if (p.getType().equals(AlgorithmType.HMAC)) {
|
||||
HmacKeyProvider h = (HmacKeyProvider) p;
|
||||
if (h.getKid() != null && h.getSecretKey() != null) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracev("Active secret realm={0} kid={1}", realm.getName(), p.getKid());
|
||||
}
|
||||
String kid = p.getKid();
|
||||
return new ActiveHmacKey(kid, h.getSecretKey());
|
||||
}
|
||||
String kid = p.getKid();
|
||||
return new ActiveKey(kid, p.getPrivateKey(), p.getPublicKey(kid), p.getCertificate(kid));
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Failed to get keys");
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKey getPublicKey(RealmModel realm, String kid) {
|
||||
public PublicKey getRsaPublicKey(RealmModel realm, String kid) {
|
||||
if (kid == null) {
|
||||
logger.warnv("KID is null, can't find public key", realm.getName(), kid);
|
||||
return null;
|
||||
}
|
||||
|
||||
for (KeyProvider p : getProviders(realm)) {
|
||||
PublicKey publicKey = p.getPublicKey(kid);
|
||||
if (publicKey != null) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracev("Found public key realm={0} kid={1}", realm.getName(), kid);
|
||||
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;
|
||||
}
|
||||
return publicKey;
|
||||
}
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
|
@ -83,19 +108,22 @@ public class DefaultKeyManager implements KeyManager {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Certificate getCertificate(RealmModel realm, String kid) {
|
||||
public Certificate getRsaCertificate(RealmModel realm, String kid) {
|
||||
if (kid == null) {
|
||||
logger.warnv("KID is null, can't find public key", realm.getName(), kid);
|
||||
return null;
|
||||
}
|
||||
|
||||
for (KeyProvider p : getProviders(realm)) {
|
||||
Certificate certificate = p.getCertificate(kid);
|
||||
if (certificate != null) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracev("Found certificate realm={0} kid={1}", realm.getName(), kid);
|
||||
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;
|
||||
}
|
||||
return certificate;
|
||||
}
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
|
@ -105,20 +133,63 @@ public class DefaultKeyManager implements KeyManager {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<KeyMetadata> getKeys(RealmModel realm, boolean includeDisabled) {
|
||||
List<KeyMetadata> keys = new LinkedList<>();
|
||||
public SecretKey getHmacSecretKey(RealmModel realm, String kid) {
|
||||
if (kid == null) {
|
||||
logger.warnv("KID is null, can't find public key", realm.getName(), kid);
|
||||
return null;
|
||||
}
|
||||
|
||||
for (KeyProvider p : getProviders(realm)) {
|
||||
if (includeDisabled) {
|
||||
keys.addAll(p.getKeyMetadata());
|
||||
} else {
|
||||
p.getKeyMetadata().stream().filter(k -> k.getStatus() != KeyMetadata.Status.DISABLED).forEach(k -> keys.add(k));
|
||||
if (p.getType().equals(AlgorithmType.HMAC)) {
|
||||
HmacKeyProvider h = (HmacKeyProvider) p;
|
||||
SecretKey s = h.getSecretKey(kid);
|
||||
if (s != null) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracev("Found secret key realm={0} kid={1}", realm.getName(), kid);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracev("Failed to find secret key realm={0} kid={1}", realm.getName(), kid);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RsaKeyMetadata> getRsaKeys(RealmModel realm, boolean includeDisabled) {
|
||||
List<RsaKeyMetadata> keys = new LinkedList<>();
|
||||
for (KeyProvider p : getProviders(realm)) {
|
||||
if (p instanceof RsaKeyProvider) {
|
||||
if (includeDisabled) {
|
||||
keys.addAll(p.getKeyMetadata());
|
||||
} else {
|
||||
List<RsaKeyMetadata> metadata = p.getKeyMetadata();
|
||||
metadata.stream().filter(k -> k.getStatus() != KeyMetadata.Status.DISABLED).forEach(k -> keys.add(k));
|
||||
}
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HmacKeyMetadata> getHmacKeys(RealmModel realm, boolean includeDisabled) {
|
||||
List<HmacKeyMetadata> keys = new LinkedList<>();
|
||||
for (KeyProvider p : getProviders(realm)) {
|
||||
if (p instanceof HmacKeyProvider) {
|
||||
if (includeDisabled) {
|
||||
keys.addAll(p.getKeyMetadata());
|
||||
} else {
|
||||
List<HmacKeyMetadata> metadata = p.getKeyMetadata();
|
||||
metadata.stream().filter(k -> k.getStatus() != KeyMetadata.Status.DISABLED).forEach(k -> keys.add(k));
|
||||
}
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
private List<KeyProvider> getProviders(RealmModel realm) {
|
||||
boolean active = false;
|
||||
List<KeyProvider> providers = providersMap.get(realm.getId());
|
||||
if (providers == null) {
|
||||
providers = new LinkedList<>();
|
||||
|
@ -126,6 +197,9 @@ public class DefaultKeyManager implements KeyManager {
|
|||
List<ComponentModel> components = new LinkedList<>(realm.getComponents(realm.getId(), KeyProvider.class.getName()));
|
||||
components.sort(new ProviderComparator());
|
||||
|
||||
boolean activeRsa = false;
|
||||
boolean activeHmac = false;
|
||||
|
||||
for (ComponentModel c : components) {
|
||||
try {
|
||||
ProviderFactory<KeyProvider> f = session.getKeycloakSessionFactory().getProviderFactory(KeyProvider.class, c.getProviderId());
|
||||
|
@ -133,18 +207,30 @@ public class DefaultKeyManager implements KeyManager {
|
|||
KeyProvider provider = factory.create(session, c);
|
||||
session.enlistForClose(provider);
|
||||
providers.add(provider);
|
||||
if (!active && provider.getKid() != null && provider.getPrivateKey() != null) {
|
||||
active = true;
|
||||
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;
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger.errorv(t, "Failed to load provider {0}", c.getId());
|
||||
}
|
||||
}
|
||||
|
||||
if (!active) {
|
||||
if (!activeRsa) {
|
||||
providers.add(new FailsafeRsaKeyProvider());
|
||||
}
|
||||
|
||||
if (!activeHmac) {
|
||||
providers.add(new FailsafeHmacKeyProvider());
|
||||
}
|
||||
|
||||
providersMap.put(realm.getId(), providers);
|
||||
}
|
||||
return providers;
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.keys;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.KeyUtils;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class FailsafeHmacKeyProvider implements HmacKeyProvider {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(FailsafeHmacKeyProvider.class);
|
||||
|
||||
private static String KID;
|
||||
|
||||
private static SecretKey KEY;
|
||||
|
||||
private static long EXPIRES;
|
||||
|
||||
private SecretKey key;
|
||||
|
||||
private String kid;
|
||||
|
||||
public FailsafeHmacKeyProvider() {
|
||||
logger.errorv("No active keys found, using failsafe provider, please login to admin console to add keys. Clustering is not supported.");
|
||||
|
||||
synchronized (FailsafeHmacKeyProvider.class) {
|
||||
if (EXPIRES < Time.currentTime()) {
|
||||
KEY = KeyUtils.loadSecretKey(KeycloakModelUtils.generateSecret(32));
|
||||
KID = KeycloakModelUtils.generateId();
|
||||
EXPIRES = Time.currentTime() + 60 * 10;
|
||||
|
||||
if (EXPIRES > 0) {
|
||||
logger.warnv("Keys expired, re-generated kid={0}", KID);
|
||||
}
|
||||
}
|
||||
|
||||
kid = KID;
|
||||
key = KEY;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKid() {
|
||||
return kid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey getSecretKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey getSecretKey(String kid) {
|
||||
return kid.equals(this.kid) ? key : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HmacKeyMetadata> getKeyMetadata() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
}
|
|
@ -31,7 +31,7 @@ import java.util.List;
|
|||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class FailsafeRsaKeyProvider implements KeyProvider {
|
||||
public class FailsafeRsaKeyProvider implements RsaKeyProvider {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(FailsafeRsaKeyProvider.class);
|
||||
|
||||
|
@ -85,7 +85,7 @@ public class FailsafeRsaKeyProvider implements KeyProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<KeyMetadata> getKeyMetadata() {
|
||||
public List<RsaKeyMetadata> getKeyMetadata() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.keys;
|
||||
|
||||
import org.keycloak.common.util.KeyUtils;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.jose.jws.AlgorithmType;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class GeneratedHmacKeyProvider implements HmacKeyProvider {
|
||||
|
||||
private final boolean enabled;
|
||||
|
||||
private final boolean active;
|
||||
|
||||
private final ComponentModel model;
|
||||
private final String kid;
|
||||
private final SecretKey secretKey;
|
||||
|
||||
public GeneratedHmacKeyProvider(ComponentModel model) {
|
||||
this.enabled = model.get(Attributes.ENABLED_KEY, true);
|
||||
this.active = model.get(Attributes.ACTIVE_KEY, true);
|
||||
this.kid = model.get(Attributes.KID_KEY);
|
||||
this.model = model;
|
||||
|
||||
if (model.hasNote(SecretKey.class.getName())) {
|
||||
secretKey = model.getNote(SecretKey.class.getName());
|
||||
} else {
|
||||
secretKey = KeyUtils.loadSecretKey(model.get(Attributes.SECRET_KEY));
|
||||
model.setNote(SecretKey.class.getName(), secretKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey getSecretKey() {
|
||||
return isActive() ? secretKey : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey getSecretKey(String kid) {
|
||||
return isEnabled() && kid.equals(this.kid) ? secretKey : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKid() {
|
||||
return isActive() ? kid : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HmacKeyMetadata> getKeyMetadata() {
|
||||
if (kid != null && secretKey != null) {
|
||||
HmacKeyMetadata k = new HmacKeyMetadata();
|
||||
k.setProviderId(model.getId());
|
||||
k.setProviderPriority(model.get(Attributes.PRIORITY_KEY, 0l));
|
||||
k.setKid(kid);
|
||||
if (isActive()) {
|
||||
k.setStatus(KeyMetadata.Status.ACTIVE);
|
||||
} else if (isEnabled()) {
|
||||
k.setStatus(KeyMetadata.Status.PASSIVE);
|
||||
} else {
|
||||
k.setStatus(KeyMetadata.Status.DISABLED);
|
||||
}
|
||||
return Collections.singletonList(k);
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
private boolean isEnabled() {
|
||||
return secretKey != null && enabled;
|
||||
}
|
||||
|
||||
private boolean isActive() {
|
||||
return isEnabled() && active;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.keys;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.component.ComponentValidationException;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.provider.ConfigurationValidationHelper;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class GeneratedHmacKeyProviderFactory extends AbstractHmacKeyProviderFactory {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(GeneratedHmacKeyProviderFactory.class);
|
||||
|
||||
public static final String ID = "hmac-generated";
|
||||
|
||||
private static final String HELP_TEXT = "Generates HMAC secret key";
|
||||
|
||||
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = AbstractHmacKeyProviderFactory.configurationBuilder()
|
||||
.property(Attributes.SECRET_SIZE_PROPERTY)
|
||||
.build();
|
||||
|
||||
@Override
|
||||
public KeyProvider create(KeycloakSession session, ComponentModel model) {
|
||||
return new GeneratedHmacKeyProvider(model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return HELP_TEXT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return CONFIG_PROPERTIES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException {
|
||||
ConfigurationValidationHelper.check(model).checkList(Attributes.SECRET_SIZE_PROPERTY, false);
|
||||
|
||||
int size = model.get(Attributes.SECRET_SIZE_KEY, 32);
|
||||
|
||||
if (!(model.contains(Attributes.SECRET_KEY))) {
|
||||
generateSecret(model, size);
|
||||
logger.debugv("Generated secret for {0}", realm.getName());
|
||||
} else {
|
||||
int currentSize = Base64Url.decode(model.get(Attributes.SECRET_KEY)).length;
|
||||
if (currentSize != size) {
|
||||
generateSecret(model, size);
|
||||
logger.debugv("Secret size changed, generating new secret for {0}", realm.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void generateSecret(ComponentModel model, int size) {
|
||||
try {
|
||||
String secret = KeycloakModelUtils.generateSecret(size);
|
||||
model.put(Attributes.SECRET_KEY, secret);
|
||||
|
||||
String kid = KeycloakModelUtils.generateId();
|
||||
model.put(Attributes.KID_KEY, kid);
|
||||
} catch (Throwable t) {
|
||||
throw new ComponentValidationException("Failed to generate secret", t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
}
|
|
@ -53,26 +53,16 @@ public class GeneratedRsaKeyProviderFactory extends AbstractRsaKeyProviderFactor
|
|||
|
||||
@Override
|
||||
public KeyProvider create(KeycloakSession session, ComponentModel model) {
|
||||
return new RsaKeyProvider(session.getContext().getRealm(), model);
|
||||
return new ImportedRsaKeyProvider(session.getContext().getRealm(), model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException {
|
||||
super.validateConfiguration(session, realm, model);
|
||||
|
||||
ConfigurationValidationHelper.check(model)
|
||||
.checkInt(Attributes.KEY_SIZE_PROPERTY, false);
|
||||
ConfigurationValidationHelper.check(model).checkList(Attributes.KEY_SIZE_PROPERTY, false);
|
||||
|
||||
int size;
|
||||
if (!model.contains(Attributes.KEY_SIZE_KEY)) {
|
||||
size = 2048;
|
||||
model.put(Attributes.KEY_SIZE_KEY, size);
|
||||
} else {
|
||||
size = model.get(Attributes.KEY_SIZE_KEY, 2048);
|
||||
if (size != 1024 && size != 2048 && size != 4096) {
|
||||
throw new ComponentValidationException("Keysize should be 1024, 2048 or 4096");
|
||||
}
|
||||
}
|
||||
int size = model.get(Attributes.KEY_SIZE_KEY, 2048);
|
||||
|
||||
if (!(model.contains(Attributes.PRIVATE_KEY_KEY) && model.contains(Attributes.CERTIFICATE_KEY))) {
|
||||
generateKeys(realm, model, size);
|
||||
|
|
|
@ -31,9 +31,9 @@ import java.security.cert.X509Certificate;
|
|||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class RsaKeyProvider extends AbstractRsaKeyProvider {
|
||||
public class ImportedRsaKeyProvider extends AbstractRsaKeyProvider {
|
||||
|
||||
public RsaKeyProvider(RealmModel realm, ComponentModel model) {
|
||||
public ImportedRsaKeyProvider(RealmModel realm, ComponentModel model) {
|
||||
super(realm, model);
|
||||
}
|
||||
|
|
@ -33,13 +33,12 @@ import java.security.KeyPair;
|
|||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class RsaKeyProviderFactory extends AbstractRsaKeyProviderFactory {
|
||||
public class ImportedRsaKeyProviderFactory extends AbstractRsaKeyProviderFactory {
|
||||
|
||||
public static final String ID = "rsa";
|
||||
|
||||
|
@ -52,7 +51,7 @@ public class RsaKeyProviderFactory extends AbstractRsaKeyProviderFactory {
|
|||
|
||||
@Override
|
||||
public KeyProvider create(KeycloakSession session, ComponentModel model) {
|
||||
return new RsaKeyProvider(session.getContext().getRealm(), model);
|
||||
return new ImportedRsaKeyProvider(session.getContext().getRealm(), model);
|
||||
}
|
||||
|
||||
@Override
|
|
@ -22,6 +22,7 @@ import org.jboss.logging.Logger;
|
|||
import org.keycloak.common.ClientConnection;
|
||||
import org.keycloak.jose.jws.JWSBuilder;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.crypto.HMACProvider;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
|
@ -31,6 +32,7 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.util.CookieHelper;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.ws.rs.core.Cookie;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.security.PublicKey;
|
||||
|
@ -114,12 +116,11 @@ public class RestartLoginCookie {
|
|||
}
|
||||
|
||||
public String encode(KeycloakSession session, RealmModel realm) {
|
||||
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
|
||||
KeyManager.ActiveHmacKey activeKey = session.keys().getActiveHmacKey(realm);
|
||||
|
||||
JWSBuilder builder = new JWSBuilder();
|
||||
return builder.kid(keys.getKid()).jsonContent(this)
|
||||
.rsa256(keys.getPrivateKey());
|
||||
|
||||
return builder.kid(activeKey.getKid()).jsonContent(this)
|
||||
.hmac256(activeKey.getSecretKey());
|
||||
}
|
||||
|
||||
public RestartLoginCookie() {
|
||||
|
@ -157,8 +158,8 @@ public class RestartLoginCookie {
|
|||
}
|
||||
String encodedCookie = cook.getValue();
|
||||
JWSInput input = new JWSInput(encodedCookie);
|
||||
PublicKey publicKey = session.keys().getPublicKey(realm, input.getHeader().getKeyId());
|
||||
if (!RSAProvider.verify(input, publicKey)) {
|
||||
SecretKey secretKey = session.keys().getHmacSecretKey(realm, input.getHeader().getKeyId());
|
||||
if (!HMACProvider.verify(input, secretKey)) {
|
||||
logger.debug("Failed to verify encoded RestartLoginCookie");
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi
|
|||
RSATokenVerifier verifier = RSATokenVerifier.create(token)
|
||||
.realmUrl(Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName()));
|
||||
|
||||
PublicKey publicKey = session.keys().getPublicKey(realm, verifier.getHeader().getKeyId());
|
||||
PublicKey publicKey = session.keys().getRsaPublicKey(realm, verifier.getHeader().getKeyId());
|
||||
if (publicKey == null) {
|
||||
valid = false;
|
||||
} else {
|
||||
|
@ -96,7 +96,7 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi
|
|||
RSATokenVerifier verifier = RSATokenVerifier.create(token)
|
||||
.realmUrl(Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName()));
|
||||
|
||||
PublicKey publicKey = session.keys().getPublicKey(realm, verifier.getHeader().getKeyId());
|
||||
PublicKey publicKey = session.keys().getRsaPublicKey(realm, verifier.getHeader().getKeyId());
|
||||
verifier.publicKey(publicKey);
|
||||
|
||||
return verifier.verify().getToken();
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.keycloak.jose.jwk.JSONWebKeySet;
|
|||
import org.keycloak.jose.jwk.JWK;
|
||||
import org.keycloak.jose.jwk.JWKBuilder;
|
||||
import org.keycloak.keys.KeyMetadata;
|
||||
import org.keycloak.keys.RsaKeyMetadata;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpoint;
|
||||
|
@ -187,11 +188,11 @@ public class OIDCLoginProtocolService {
|
|||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@NoCache
|
||||
public Response certs() {
|
||||
List<KeyMetadata> publicKeys = session.keys().getKeys(realm, false);
|
||||
List<RsaKeyMetadata> publicKeys = session.keys().getRsaKeys(realm, false);
|
||||
JWK[] keys = new JWK[publicKeys.size()];
|
||||
|
||||
int i = 0;
|
||||
for (KeyMetadata k : publicKeys) {
|
||||
for (RsaKeyMetadata k : publicKeys) {
|
||||
keys[i++] = JWKBuilder.create().kid(k.getKid()).rs256(k.getPublicKey());
|
||||
}
|
||||
|
||||
|
|
|
@ -287,7 +287,7 @@ public class TokenManager {
|
|||
public RefreshToken toRefreshToken(KeycloakSession session, RealmModel realm, String encodedRefreshToken) throws JWSInputException, OAuthErrorException {
|
||||
JWSInput jws = new JWSInput(encodedRefreshToken);
|
||||
|
||||
if (!RSAProvider.verify(jws, session.keys().getPublicKey(realm, jws.getHeader().getKeyId()))) {
|
||||
if (!RSAProvider.verify(jws, session.keys().getRsaPublicKey(realm, jws.getHeader().getKeyId()))) {
|
||||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token");
|
||||
}
|
||||
|
||||
|
@ -298,7 +298,7 @@ public class TokenManager {
|
|||
try {
|
||||
JWSInput jws = new JWSInput(encodedIDToken);
|
||||
IDToken idToken;
|
||||
if (!RSAProvider.verify(jws, session.keys().getPublicKey(realm, jws.getHeader().getKeyId()))) {
|
||||
if (!RSAProvider.verify(jws, session.keys().getRsaPublicKey(realm, jws.getHeader().getKeyId()))) {
|
||||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid IDToken");
|
||||
}
|
||||
idToken = jws.readJsonContent(IDToken.class);
|
||||
|
@ -626,8 +626,8 @@ public class TokenManager {
|
|||
}
|
||||
|
||||
public String encodeToken(KeycloakSession session, RealmModel realm, Object token) {
|
||||
KeyManager.ActiveKey activeKey = session.keys().getActiveKey(realm);
|
||||
return new JWSBuilder().type(JWT).kid(activeKey.getKid()).jsonContent(token).sign(jwsAlgorithm, activeKey.getPrivateKey());
|
||||
KeyManager.ActiveRsaKey activeRsaKey = session.keys().getActiveRsaKey(realm);
|
||||
return new JWSBuilder().type(JWT).kid(activeRsaKey.getKid()).jsonContent(token).sign(jwsAlgorithm, activeRsaKey.getPrivateKey());
|
||||
}
|
||||
|
||||
public AccessTokenResponseBuilder responseBuilder(RealmModel realm, ClientModel client, EventBuilder event, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||
|
@ -734,7 +734,7 @@ public class TokenManager {
|
|||
|
||||
|
||||
public AccessTokenResponse build() {
|
||||
KeyManager.ActiveKey activeKey = session.keys().getActiveKey(realm);
|
||||
KeyManager.ActiveRsaKey activeRsaKey = session.keys().getActiveRsaKey(realm);
|
||||
|
||||
if (accessToken != null) {
|
||||
event.detail(Details.TOKEN_ID, accessToken.getId());
|
||||
|
@ -751,7 +751,7 @@ public class TokenManager {
|
|||
|
||||
AccessTokenResponse res = new AccessTokenResponse();
|
||||
if (accessToken != null) {
|
||||
String encodedToken = new JWSBuilder().type(JWT).kid(activeKey.getKid()).jsonContent(accessToken).sign(jwsAlgorithm, activeKey.getPrivateKey());
|
||||
String encodedToken = new JWSBuilder().type(JWT).kid(activeRsaKey.getKid()).jsonContent(accessToken).sign(jwsAlgorithm, activeRsaKey.getPrivateKey());
|
||||
res.setToken(encodedToken);
|
||||
res.setTokenType("bearer");
|
||||
res.setSessionState(accessToken.getSessionState());
|
||||
|
@ -769,11 +769,11 @@ public class TokenManager {
|
|||
}
|
||||
|
||||
if (idToken != null) {
|
||||
String encodedToken = new JWSBuilder().type(JWT).kid(activeKey.getKid()).jsonContent(idToken).sign(jwsAlgorithm, activeKey.getPrivateKey());
|
||||
String encodedToken = new JWSBuilder().type(JWT).kid(activeRsaKey.getKid()).jsonContent(idToken).sign(jwsAlgorithm, activeRsaKey.getPrivateKey());
|
||||
res.setIdToken(encodedToken);
|
||||
}
|
||||
if (refreshToken != null) {
|
||||
String encodedToken = new JWSBuilder().type(JWT).kid(activeKey.getKid()).jsonContent(refreshToken).sign(jwsAlgorithm, activeKey.getPrivateKey());
|
||||
String encodedToken = new JWSBuilder().type(JWT).kid(activeRsaKey.getKid()).jsonContent(refreshToken).sign(jwsAlgorithm, activeRsaKey.getPrivateKey());
|
||||
res.setRefreshToken(encodedToken);
|
||||
if (refreshToken.getExpiration() != 0) {
|
||||
res.setRefreshExpiresIn(refreshToken.getExpiration() - Time.currentTime());
|
||||
|
|
|
@ -132,7 +132,7 @@ public class UserInfoEndpoint {
|
|||
RSATokenVerifier verifier = RSATokenVerifier.create(tokenString)
|
||||
.realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
|
||||
String kid = verifier.getHeader().getKeyId();
|
||||
verifier.publicKey(session.keys().getPublicKey(realm, kid));
|
||||
verifier.publicKey(session.keys().getRsaPublicKey(realm, kid));
|
||||
token = verifier.verify().getToken();
|
||||
} catch (VerificationException e) {
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
|
@ -194,7 +194,7 @@ public class UserInfoEndpoint {
|
|||
claims.put("aud", audience);
|
||||
|
||||
Algorithm signatureAlg = cfg.getUserInfoSignedResponseAlg();
|
||||
PrivateKey privateKey = session.keys().getActiveKey(realm).getPrivateKey();
|
||||
PrivateKey privateKey = session.keys().getActiveRsaKey(realm).getPrivateKey();
|
||||
|
||||
String signedUserInfo = new JWSBuilder()
|
||||
.jsonContent(claims)
|
||||
|
|
|
@ -382,7 +382,7 @@ public class SamlProtocol implements LoginProtocol {
|
|||
|
||||
Document samlDocument = null;
|
||||
KeyManager keyManager = session.keys();
|
||||
KeyManager.ActiveKey keys = keyManager.getActiveKey(realm);
|
||||
KeyManager.ActiveRsaKey keys = keyManager.getActiveRsaKey(realm);
|
||||
boolean postBinding = isPostBinding(clientSession);
|
||||
String keyName = samlClient.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate());
|
||||
|
||||
|
@ -518,7 +518,7 @@ public class SamlProtocol implements LoginProtocol {
|
|||
String bindingUri = getLogoutServiceUrl(uriInfo, client, SAML_REDIRECT_BINDING);
|
||||
SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(bindingUri, clientSession, client);
|
||||
if (samlClient.requiresRealmSignature() && samlClient.addExtensionsElementWithKeyInfo()) {
|
||||
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
|
||||
KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
|
||||
String keyName = samlClient.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate());
|
||||
logoutBuilder.addExtension(new KeycloakKeySamlExtensionGenerator(keyName));
|
||||
}
|
||||
|
@ -561,7 +561,7 @@ public class SamlProtocol implements LoginProtocol {
|
|||
if (canonicalization != null) {
|
||||
binding.canonicalizationMethod(canonicalization);
|
||||
}
|
||||
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
|
||||
KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
|
||||
XmlKeyInfoKeyNameTransformer transformer = XmlKeyInfoKeyNameTransformer.from(
|
||||
userSession.getNote(SAML_SERVER_SIGNATURE_KEYINFO_KEY_NAME_TRANSFORMER),
|
||||
SamlClient.DEFAULT_XML_KEY_INFO_KEY_NAME_TRANSFORMER);
|
||||
|
@ -668,7 +668,7 @@ public class SamlProtocol implements LoginProtocol {
|
|||
private JaxrsSAML2BindingBuilder createBindingBuilder(SamlClient samlClient) {
|
||||
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder();
|
||||
if (samlClient.requiresRealmSignature()) {
|
||||
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
|
||||
KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
|
||||
String keyName = samlClient.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate());
|
||||
binding.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keyName, keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import org.keycloak.events.Details;
|
|||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.keys.RsaKeyMetadata;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeyManager;
|
||||
|
@ -416,7 +417,7 @@ public class SamlService extends AuthorizationEndpointBase {
|
|||
boolean postBinding = SamlProtocol.SAML_POST_BINDING.equals(logoutBinding);
|
||||
if (samlClient.requiresRealmSignature()) {
|
||||
SignatureAlgorithm algorithm = samlClient.getSignatureAlgorithm();
|
||||
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
|
||||
KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
|
||||
binding.signatureAlgorithm(algorithm).signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
|
||||
if (! postBinding && samlClient.addExtensionsElementWithKeyInfo()) { // Only include extension if REDIRECT binding and signing whole SAML protocol message
|
||||
builder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid()));
|
||||
|
@ -567,18 +568,18 @@ public class SamlService extends AuthorizationEndpointBase {
|
|||
props.put("idp.sso.HTTP-Redirect", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
|
||||
props.put("idp.sls.HTTP-POST", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
|
||||
StringBuilder keysString = new StringBuilder();
|
||||
Set<KeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list
|
||||
Set<RsaKeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list
|
||||
? (int) (o2.getProviderPriority() - o1.getProviderPriority())
|
||||
: (o1.getStatus() == KeyMetadata.Status.PASSIVE ? 1 : -1));
|
||||
keys.addAll(session.keys().getKeys(realm, false));
|
||||
for (KeyMetadata key : keys) {
|
||||
keys.addAll(session.keys().getRsaKeys(realm, false));
|
||||
for (RsaKeyMetadata key : keys) {
|
||||
addKeyInfo(keysString, key, KeyTypes.SIGNING.value());
|
||||
}
|
||||
props.put("idp.signing.certificates", keysString.toString());
|
||||
return StringPropertyReplacer.replaceProperties(template, props);
|
||||
}
|
||||
|
||||
private static void addKeyInfo(StringBuilder target, KeyMetadata key, String purpose) {
|
||||
private static void addKeyInfo(StringBuilder target, RsaKeyMetadata key, String purpose) {
|
||||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.protocol.saml.installation;
|
|||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.common.util.PemUtils;
|
||||
import org.keycloak.keys.RsaKeyMetadata;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
@ -87,11 +88,11 @@ public class SamlIDPDescriptorClientInstallation implements ClientInstallationPr
|
|||
}
|
||||
|
||||
// keys
|
||||
Set<KeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list
|
||||
Set<RsaKeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list
|
||||
? (int) (o2.getProviderPriority() - o1.getProviderPriority())
|
||||
: (o1.getStatus() == KeyMetadata.Status.PASSIVE ? 1 : -1));
|
||||
keys.addAll(session.keys().getKeys(realm, false));
|
||||
for (KeyMetadata key : keys) {
|
||||
keys.addAll(session.keys().getRsaKeys(realm, false));
|
||||
for (RsaKeyMetadata key : keys) {
|
||||
addKeyInfo(sb, key, KeyTypes.SIGNING.value());
|
||||
}
|
||||
|
||||
|
@ -100,7 +101,7 @@ public class SamlIDPDescriptorClientInstallation implements ClientInstallationPr
|
|||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void addKeyInfo(StringBuilder target, KeyMetadata key, String purpose) {
|
||||
private static void addKeyInfo(StringBuilder target, RsaKeyMetadata key, String purpose) {
|
||||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ public class ClientRegistrationTokenUtils {
|
|||
return TokenVerification.error(new RuntimeException("Invalid token", e));
|
||||
}
|
||||
|
||||
PublicKey publicKey = session.keys().getPublicKey(realm, input.getHeader().getKeyId());
|
||||
PublicKey publicKey = session.keys().getRsaPublicKey(realm, input.getHeader().getKeyId());
|
||||
|
||||
if (!RSAProvider.verify(input, publicKey)) {
|
||||
return TokenVerification.error(new RuntimeException("Failed verify token"));
|
||||
|
@ -115,7 +115,7 @@ public class ClientRegistrationTokenUtils {
|
|||
jwt.issuer(issuer);
|
||||
jwt.audience(issuer);
|
||||
|
||||
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
|
||||
KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
|
||||
|
||||
String token = new JWSBuilder().kid(keys.getKid()).jsonContent(jwt).rsa256(keys.getPrivateKey());
|
||||
return token;
|
||||
|
|
|
@ -19,7 +19,7 @@ package org.keycloak.services.managers;
|
|||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.RSATokenVerifier;
|
||||
import org.keycloak.TokenVerifier;
|
||||
import org.keycloak.authentication.RequiredActionContext;
|
||||
import org.keycloak.authentication.RequiredActionContextResult;
|
||||
import org.keycloak.authentication.RequiredActionFactory;
|
||||
|
@ -33,6 +33,7 @@ import org.keycloak.events.Errors;
|
|||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.forms.login.LoginFormsProvider;
|
||||
import org.keycloak.jose.jws.AlgorithmType;
|
||||
import org.keycloak.jose.jws.JWSBuilder;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
|
@ -58,6 +59,7 @@ import org.keycloak.services.resources.RealmsResource;
|
|||
import org.keycloak.services.util.CookieHelper;
|
||||
import org.keycloak.services.util.P3PHelper;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.ws.rs.core.Cookie;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
|
@ -113,12 +115,12 @@ public class AuthenticationManager {
|
|||
if (cookie == null) return;
|
||||
String tokenString = cookie.getValue();
|
||||
|
||||
RSATokenVerifier verifier = RSATokenVerifier.create(tokenString).realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())).checkActive(false).checkTokenType(false);
|
||||
TokenVerifier verifier = TokenVerifier.create(tokenString).realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())).checkActive(false).checkTokenType(false);
|
||||
|
||||
String kid = verifier.getHeader().getKeyId();
|
||||
PublicKey publicKey = session.keys().getPublicKey(realm, kid);
|
||||
SecretKey secretKey = session.keys().getHmacSecretKey(realm, kid);
|
||||
|
||||
AccessToken token = verifier.publicKey(publicKey).verify().getToken();
|
||||
AccessToken token = verifier.secretKey(secretKey).verify().getToken();
|
||||
UserSessionModel cookieSession = session.sessions().getUserSession(realm, token.getSessionState());
|
||||
if (cookieSession == null || !cookieSession.getId().equals(userSession.getId())) return;
|
||||
expireIdentityCookie(realm, uriInfo, connection);
|
||||
|
@ -337,14 +339,14 @@ public class AuthenticationManager {
|
|||
}
|
||||
|
||||
protected static String encodeToken(KeycloakSession session, RealmModel realm, Object token) {
|
||||
KeyManager.ActiveKey activeKey = session.keys().getActiveKey(realm);
|
||||
KeyManager.ActiveHmacKey activeKey = session.keys().getActiveHmacKey(realm);
|
||||
|
||||
logger.tracef("Encoding token with kid '%s'", activeKey.getKid());
|
||||
|
||||
String encodedToken = new JWSBuilder()
|
||||
.kid(activeKey.getKid())
|
||||
.jsonContent(token)
|
||||
.rsa256(activeKey.getPrivateKey());
|
||||
.hmac256(activeKey.getSecretKey());
|
||||
return encodedToken;
|
||||
}
|
||||
|
||||
|
@ -691,15 +693,25 @@ public class AuthenticationManager {
|
|||
protected static AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, boolean checkTokenType,
|
||||
String tokenString, HttpHeaders headers) {
|
||||
try {
|
||||
RSATokenVerifier verifier = RSATokenVerifier.create(tokenString).realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())).checkActive(checkActive).checkTokenType(checkTokenType);
|
||||
TokenVerifier verifier = TokenVerifier.create(tokenString).realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())).checkActive(checkActive).checkTokenType(checkTokenType);
|
||||
String kid = verifier.getHeader().getKeyId();
|
||||
AlgorithmType algorithmType = verifier.getHeader().getAlgorithm().getType();
|
||||
|
||||
PublicKey publicKey = session.keys().getPublicKey(realm, kid);
|
||||
if (publicKey == null) {
|
||||
logger.debugf("Identity cookie signed with unknown kid '%s'", kid);
|
||||
return null;
|
||||
if (AlgorithmType.RSA.equals(algorithmType)) {
|
||||
PublicKey publicKey = session.keys().getRsaPublicKey(realm, kid);
|
||||
if (publicKey == null) {
|
||||
logger.debugf("Identity cookie signed with unknown kid '%s'", kid);
|
||||
return null;
|
||||
}
|
||||
verifier.publicKey(publicKey);
|
||||
} else if (AlgorithmType.HMAC.equals(algorithmType)) {
|
||||
SecretKey secretKey = session.keys().getHmacSecretKey(realm, kid);
|
||||
if (secretKey == null) {
|
||||
logger.debugf("Identity cookie signed with unknown kid '%s'", kid);
|
||||
return null;
|
||||
}
|
||||
verifier.secretKey(secretKey);
|
||||
}
|
||||
verifier.publicKey(publicKey);
|
||||
|
||||
AccessToken token = verifier.verify().getToken();
|
||||
if (checkActive) {
|
||||
|
|
|
@ -93,7 +93,7 @@ public class PublicRealmResource {
|
|||
rep.setTokenServiceUrl(OIDCLoginProtocolService.tokenServiceBaseUrl(uriInfo).build(realm.getName()).toString());
|
||||
rep.setAccountServiceUrl(AccountService.accountServiceBaseUrl(uriInfo).build(realm.getName()).toString());
|
||||
rep.setAdminApiUrl(uriInfo.getBaseUriBuilder().path(AdminRoot.class).build().toString());
|
||||
rep.setPublicKeyPem(PemUtils.encodeKey(session.keys().getActiveKey(realm).getPublicKey()));
|
||||
rep.setPublicKeyPem(PemUtils.encodeKey(session.keys().getActiveRsaKey(realm).getPublicKey()));
|
||||
rep.setNotBefore(realm.getNotBefore());
|
||||
return rep;
|
||||
}
|
||||
|
|
|
@ -34,7 +34,6 @@ import org.keycloak.models.KeyManager;
|
|||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.oidc.utils.JWKSHttpUtils;
|
||||
import org.keycloak.representations.KeyStoreConfig;
|
||||
import org.keycloak.representations.idm.CertificateRepresentation;
|
||||
import org.keycloak.services.ErrorResponseException;
|
||||
|
@ -374,8 +373,8 @@ public class ClientAttributeCertificateResource {
|
|||
|
||||
if (config.isRealmCertificate() == null || config.isRealmCertificate().booleanValue()) {
|
||||
KeyManager keys = session.keys();
|
||||
String kid = keys.getActiveKey(realm).getKid();
|
||||
Certificate certificate = keys.getCertificate(realm, kid);
|
||||
String kid = keys.getActiveRsaKey(realm).getKid();
|
||||
Certificate certificate = keys.getRsaCertificate(realm, kid);
|
||||
String certificateAlias = config.getRealmAlias();
|
||||
if (certificateAlias == null) certificateAlias = realm.getName();
|
||||
keyStore.setCertificateEntry(certificateAlias, certificate);
|
||||
|
|
|
@ -19,7 +19,9 @@ package org.keycloak.services.resources.admin;
|
|||
|
||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||
import org.keycloak.common.util.PemUtils;
|
||||
import org.keycloak.keys.KeyMetadata;
|
||||
import org.keycloak.jose.jws.AlgorithmType;
|
||||
import org.keycloak.keys.HmacKeyMetadata;
|
||||
import org.keycloak.keys.RsaKeyMetadata;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeyManager;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
@ -28,9 +30,10 @@ import org.keycloak.representations.idm.KeysMetadataRepresentation;
|
|||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import java.util.Collections;
|
||||
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>
|
||||
|
@ -56,20 +59,33 @@ public class KeyResource {
|
|||
KeyManager keystore = session.keys();
|
||||
|
||||
KeysMetadataRepresentation keys = new KeysMetadataRepresentation();
|
||||
keys.setActive(Collections.singletonMap(KeyMetadata.Type.RSA.name(), keystore.getActiveKey(realm).getKid()));
|
||||
|
||||
Map<String, String> active = new HashMap<>();
|
||||
active.put(AlgorithmType.RSA.name(), keystore.getActiveRsaKey(realm).getKid());
|
||||
active.put(AlgorithmType.HMAC.name(), keystore.getActiveHmacKey(realm).getKid());
|
||||
keys.setActive(active);
|
||||
|
||||
List<KeysMetadataRepresentation.KeyMetadataRepresentation> l = new LinkedList<>();
|
||||
for (KeyMetadata m : session.keys().getKeys(realm, true)) {
|
||||
for (RsaKeyMetadata m : session.keys().getRsaKeys(realm, true)) {
|
||||
KeysMetadataRepresentation.KeyMetadataRepresentation r = new KeysMetadataRepresentation.KeyMetadataRepresentation();
|
||||
r.setProviderId(m.getProviderId());
|
||||
r.setProviderPriority(m.getProviderPriority());
|
||||
r.setKid(m.getKid());
|
||||
r.setStatus(m.getStatus() != null ? m.getStatus().name() : null);
|
||||
r.setType(m.getType() != null ? m.getType().name() : null);
|
||||
r.setType(AlgorithmType.RSA.name());
|
||||
r.setPublicKey(PemUtils.encodeKey(m.getPublicKey()));
|
||||
r.setCertificate(PemUtils.encodeCertificate(m.getCertificate()));
|
||||
l.add(r);
|
||||
}
|
||||
for (HmacKeyMetadata m : session.keys().getHmacKeys(realm, true)) {
|
||||
KeysMetadataRepresentation.KeyMetadataRepresentation r = new KeysMetadataRepresentation.KeyMetadataRepresentation();
|
||||
r.setProviderId(m.getProviderId());
|
||||
r.setProviderPriority(m.getProviderPriority());
|
||||
r.setKid(m.getKid());
|
||||
r.setStatus(m.getStatus() != null ? m.getStatus().name() : null);
|
||||
r.setType(AlgorithmType.HMAC.name());
|
||||
l.add(r);
|
||||
}
|
||||
|
||||
keys.setKeys(l);
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
# limitations under the License.
|
||||
#
|
||||
|
||||
org.keycloak.keys.GeneratedHmacKeyProviderFactory
|
||||
org.keycloak.keys.GeneratedRsaKeyProviderFactory
|
||||
org.keycloak.keys.JavaKeystoreKeyProviderFactory
|
||||
org.keycloak.keys.RsaKeyProviderFactory
|
||||
org.keycloak.keys.ImportedRsaKeyProviderFactory
|
|
@ -20,6 +20,7 @@ package org.keycloak.testsuite.rest;
|
|||
import org.infinispan.Cache;
|
||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||
import org.jboss.resteasy.spi.BadRequestException;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
|
@ -633,6 +634,14 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
|||
return reps;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/component")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public MultivaluedHashMap<String, String> getComponentConfig(@QueryParam("componentId") String componentId) {
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
return realm.getComponent(componentId).getConfig();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/smtp-config")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package org.keycloak.testsuite.client.resources;
|
||||
|
||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.representations.idm.AdminEventRepresentation;
|
||||
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
|
||||
import org.keycloak.representations.idm.EventRepresentation;
|
||||
|
@ -251,4 +252,8 @@ public interface TestingResource {
|
|||
@Produces(MediaType.APPLICATION_JSON)
|
||||
Map<String, String> getIdentityProviderConfig(@QueryParam("alias") String alias);
|
||||
|
||||
@GET
|
||||
@Path("/component")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
MultivaluedHashMap<String, String> getComponentConfig(@QueryParam("componentId") String componentId);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.testsuite.adapter.servlet;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
|
@ -41,6 +42,7 @@ import org.keycloak.common.util.MultivaluedHashMap;
|
|||
import org.keycloak.common.util.StreamUtil;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.jose.jws.AlgorithmType;
|
||||
import org.keycloak.keys.KeyProvider;
|
||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||
|
@ -48,6 +50,7 @@ import org.keycloak.representations.AccessToken;
|
|||
import org.keycloak.representations.adapters.action.GlobalRequestResult;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.ComponentRepresentation;
|
||||
import org.keycloak.representations.idm.KeysMetadataRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
|
||||
import org.keycloak.testsuite.adapter.filter.AdapterActionsFilter;
|
||||
|
@ -58,9 +61,7 @@ import org.keycloak.testsuite.admin.ApiUtil;
|
|||
import org.keycloak.testsuite.util.URLAssert;
|
||||
import org.openqa.selenium.By;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
|
||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals;
|
||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
|
||||
|
@ -201,9 +202,9 @@ public abstract class AbstractOIDCPublicKeyRotationAdapterTest extends AbstractS
|
|||
Assert.assertEquals(200, status);
|
||||
|
||||
// Re-generate realm public key and remove the old key
|
||||
String oldKeyId = getActiveKeyId();
|
||||
String oldActiveKeyProviderId = getActiveKeyProvider();
|
||||
generateNewRealmKey();
|
||||
adminClient.realm(DEMO).components().component(oldKeyId).remove();
|
||||
adminClient.realm(DEMO).components().component(oldActiveKeyProviderId).remove();
|
||||
|
||||
// Send REST request to the customer-db app. Should be still succcessfully authenticated as the JWKPublicKeyLocator cache is still valid
|
||||
status = invokeRESTEndpoint(accessTokenString);
|
||||
|
@ -237,7 +238,8 @@ public abstract class AbstractOIDCPublicKeyRotationAdapterTest extends AbstractS
|
|||
String accessTokenString = tokenMinTTLPage.getAccessTokenString();
|
||||
|
||||
// Generate new realm public key
|
||||
String oldKeyId = getActiveKeyId();
|
||||
String oldActiveKeyProviderId = getActiveKeyProvider();
|
||||
|
||||
generateNewRealmKey();
|
||||
|
||||
// Send REST request to customer-db app. It should be successfully authenticated even that token is signed by the old key
|
||||
|
@ -245,7 +247,7 @@ public abstract class AbstractOIDCPublicKeyRotationAdapterTest extends AbstractS
|
|||
Assert.assertEquals(200, status);
|
||||
|
||||
// Remove the old realm key now
|
||||
adminClient.realm(DEMO).components().component(oldKeyId).remove();
|
||||
adminClient.realm(DEMO).components().component(oldActiveKeyProviderId).remove();
|
||||
|
||||
// Set some offset to ensure pushing notBefore will pass
|
||||
setAdapterAndServerTimeOffset(130, customerDb.toString() + "/unsecured/foo", tokenMinTTLPage.toString() + "/unsecured/foo");
|
||||
|
@ -295,13 +297,17 @@ public abstract class AbstractOIDCPublicKeyRotationAdapterTest extends AbstractS
|
|||
response.close();
|
||||
}
|
||||
|
||||
private String getActiveKeyId() {
|
||||
String realmId = adminClient.realm(DEMO).toRepresentation().getId();
|
||||
return adminClient.realm(DEMO).components().query(realmId, KeyProvider.class.getName())
|
||||
.get(0).getId();
|
||||
private String getActiveKeyProvider() {
|
||||
KeysMetadataRepresentation keyMetadata = adminClient.realm(DEMO).keys().getKeyMetadata();
|
||||
String activeKid = keyMetadata.getActive().get(AlgorithmType.RSA.name());
|
||||
for (KeysMetadataRepresentation.KeyMetadataRepresentation rep : keyMetadata.getKeys()) {
|
||||
if (rep.getKid().equals(activeKid)) {
|
||||
return rep.getProviderId();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private int invokeRESTEndpoint(String accessTokenString) {
|
||||
|
||||
HttpClient client = new DefaultHttpClient();
|
||||
|
|
|
@ -30,8 +30,7 @@ import org.keycloak.common.util.KeyUtils;
|
|||
import org.keycloak.common.util.PemUtils;
|
||||
import org.keycloak.keys.Attributes;
|
||||
import org.keycloak.keys.KeyProvider;
|
||||
import org.keycloak.keys.RsaKeyProviderFactory;
|
||||
import org.keycloak.protocol.saml.SamlClient;
|
||||
import org.keycloak.keys.ImportedRsaKeyProviderFactory;
|
||||
import org.keycloak.protocol.saml.SamlConfigAttributes;
|
||||
import org.keycloak.representations.idm.ComponentRepresentation;
|
||||
import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
|
||||
|
@ -452,7 +451,7 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
|||
ComponentRepresentation rep = new ComponentRepresentation();
|
||||
rep.setName("mycomponent");
|
||||
rep.setParentId("demo");
|
||||
rep.setProviderId(RsaKeyProviderFactory.ID);
|
||||
rep.setProviderId(ImportedRsaKeyProviderFactory.ID);
|
||||
rep.setProviderType(KeyProvider.class.getName());
|
||||
|
||||
org.keycloak.common.util.MultivaluedHashMap config = new org.keycloak.common.util.MultivaluedHashMap();
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.keys;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.common.util.PemUtils;
|
||||
import org.keycloak.jose.jws.AlgorithmType;
|
||||
import org.keycloak.jose.jws.crypto.HMACProvider;
|
||||
import org.keycloak.keys.GeneratedHmacKeyProviderFactory;
|
||||
import org.keycloak.keys.GeneratedRsaKeyProviderFactory;
|
||||
import org.keycloak.keys.KeyProvider;
|
||||
import org.keycloak.representations.idm.ComponentRepresentation;
|
||||
import org.keycloak.representations.idm.ErrorRepresentation;
|
||||
import org.keycloak.representations.idm.KeysMetadataRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class GeneratedHmacKeyProviderTest extends AbstractKeycloakTest {
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(this);
|
||||
|
||||
@Page
|
||||
protected AppPage appPage;
|
||||
|
||||
@Page
|
||||
protected LoginPage loginPage;
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class);
|
||||
testRealms.add(realm);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultKeysize() throws Exception {
|
||||
long priority = System.currentTimeMillis();
|
||||
|
||||
ComponentRepresentation rep = createRep("valid", GeneratedHmacKeyProviderFactory.ID);
|
||||
rep.setConfig(new MultivaluedHashMap<>());
|
||||
rep.getConfig().putSingle("priority", Long.toString(priority));
|
||||
|
||||
Response response = adminClient.realm("test").components().add(rep);
|
||||
String id = ApiUtil.getCreatedId(response);
|
||||
|
||||
ComponentRepresentation createdRep = adminClient.realm("test").components().component(id).toRepresentation();
|
||||
assertEquals(1, createdRep.getConfig().size());
|
||||
assertEquals(Long.toString(priority), createdRep.getConfig().getFirst("priority"));
|
||||
|
||||
KeysMetadataRepresentation keys = adminClient.realm("test").keys().getKeyMetadata();
|
||||
|
||||
KeysMetadataRepresentation.KeyMetadataRepresentation key = null;
|
||||
for (KeysMetadataRepresentation.KeyMetadataRepresentation k : keys.getKeys()) {
|
||||
if (k.getType().equals(AlgorithmType.HMAC.name())) {
|
||||
key = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(id, key.getProviderId());
|
||||
assertEquals(AlgorithmType.HMAC.name(), key.getType());
|
||||
assertEquals(priority, key.getProviderPriority());
|
||||
|
||||
assertEquals(32, Base64Url.decode(testingClient.testing("test").getComponentConfig(id).getFirst("secret")).length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void largeKeysize() throws Exception {
|
||||
long priority = System.currentTimeMillis();
|
||||
|
||||
ComponentRepresentation rep = createRep("valid", GeneratedHmacKeyProviderFactory.ID);
|
||||
rep.setConfig(new MultivaluedHashMap<>());
|
||||
rep.getConfig().putSingle("priority", Long.toString(priority));
|
||||
rep.getConfig().putSingle("secretSize", "512");
|
||||
|
||||
Response response = adminClient.realm("test").components().add(rep);
|
||||
String id = ApiUtil.getCreatedId(response);
|
||||
|
||||
ComponentRepresentation createdRep = adminClient.realm("test").components().component(id).toRepresentation();
|
||||
assertEquals(2, createdRep.getConfig().size());
|
||||
assertEquals("512", createdRep.getConfig().getFirst("secretSize"));
|
||||
|
||||
KeysMetadataRepresentation keys = adminClient.realm("test").keys().getKeyMetadata();
|
||||
|
||||
KeysMetadataRepresentation.KeyMetadataRepresentation key = null;
|
||||
for (KeysMetadataRepresentation.KeyMetadataRepresentation k : keys.getKeys()) {
|
||||
if (k.getType().equals(AlgorithmType.HMAC.name())) {
|
||||
key = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(id, key.getProviderId());
|
||||
assertEquals(AlgorithmType.HMAC.name(), key.getType());
|
||||
assertEquals(priority, key.getProviderPriority());
|
||||
|
||||
assertEquals(512, Base64Url.decode(testingClient.testing("test").getComponentConfig(id).getFirst("secret")).length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateKeysize() throws Exception {
|
||||
long priority = System.currentTimeMillis();
|
||||
|
||||
ComponentRepresentation rep = createRep("valid", GeneratedHmacKeyProviderFactory.ID);
|
||||
rep.setConfig(new MultivaluedHashMap<>());
|
||||
rep.getConfig().putSingle("priority", Long.toString(priority));
|
||||
|
||||
Response response = adminClient.realm("test").components().add(rep);
|
||||
String id = ApiUtil.getCreatedId(response);
|
||||
|
||||
assertEquals(32, Base64Url.decode(testingClient.testing("test").getComponentConfig(id).getFirst("secret")).length);
|
||||
|
||||
ComponentRepresentation createdRep = adminClient.realm("test").components().component(id).toRepresentation();
|
||||
createdRep.getConfig().putSingle("secretSize", "512");
|
||||
adminClient.realm("test").components().component(id).update(createdRep);
|
||||
|
||||
assertEquals(512, Base64Url.decode(testingClient.testing("test").getComponentConfig(id).getFirst("secret")).length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidKeysize() throws Exception {
|
||||
ComponentRepresentation rep = createRep("invalid", GeneratedHmacKeyProviderFactory.ID);
|
||||
rep.getConfig().putSingle("secretSize", "1234");
|
||||
|
||||
Response response = adminClient.realm("test").components().add(rep);
|
||||
assertErrror(response, "'Secret size' should be 32, 64, 128, 256 or 512");
|
||||
}
|
||||
|
||||
protected void assertErrror(Response response, String error) {
|
||||
if (!response.hasEntity()) {
|
||||
fail("No error message set");
|
||||
}
|
||||
|
||||
ErrorRepresentation errorRepresentation = response.readEntity(ErrorRepresentation.class);
|
||||
assertEquals(error, errorRepresentation.getErrorMessage());
|
||||
}
|
||||
|
||||
protected ComponentRepresentation createRep(String name, String providerId) {
|
||||
ComponentRepresentation rep = new ComponentRepresentation();
|
||||
rep.setName(name);
|
||||
rep.setParentId("test");
|
||||
rep.setProviderId(providerId);
|
||||
rep.setProviderType(KeyProvider.class.getName());
|
||||
rep.setConfig(new MultivaluedHashMap<>());
|
||||
return rep;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ import org.junit.Rule;
|
|||
import org.junit.Test;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.common.util.PemUtils;
|
||||
import org.keycloak.jose.jws.AlgorithmType;
|
||||
import org.keycloak.keys.GeneratedRsaKeyProviderFactory;
|
||||
import org.keycloak.keys.KeyMetadata;
|
||||
import org.keycloak.keys.KeyProvider;
|
||||
|
@ -45,7 +46,7 @@ import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
|
|||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class RsaGeneratedKeyProviderTest extends AbstractKeycloakTest {
|
||||
public class GeneratedRsaKeyProviderTest extends AbstractKeycloakTest {
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(this);
|
||||
|
@ -74,16 +75,15 @@ public class RsaGeneratedKeyProviderTest extends AbstractKeycloakTest {
|
|||
String id = ApiUtil.getCreatedId(response);
|
||||
|
||||
ComponentRepresentation createdRep = adminClient.realm("test").components().component(id).toRepresentation();
|
||||
assertEquals(2, createdRep.getConfig().size());
|
||||
assertEquals(1, createdRep.getConfig().size());
|
||||
assertEquals(Long.toString(priority), createdRep.getConfig().getFirst("priority"));
|
||||
assertEquals("2048", createdRep.getConfig().getFirst("keySize"));
|
||||
|
||||
KeysMetadataRepresentation keys = adminClient.realm("test").keys().getKeyMetadata();
|
||||
|
||||
KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0);
|
||||
|
||||
assertEquals(id, key.getProviderId());
|
||||
assertEquals(KeyMetadata.Type.RSA.name(), key.getType());
|
||||
assertEquals(AlgorithmType.RSA.name(), key.getType());
|
||||
assertEquals(priority, key.getProviderPriority());
|
||||
assertEquals(2048, ((RSAPublicKey) PemUtils.decodePublicKey(keys.getKeys().get(0).getPublicKey())).getModulus().bitLength());
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ public class RsaGeneratedKeyProviderTest extends AbstractKeycloakTest {
|
|||
KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0);
|
||||
|
||||
assertEquals(id, key.getProviderId());
|
||||
assertEquals(KeyMetadata.Type.RSA.name(), key.getType());
|
||||
assertEquals(AlgorithmType.RSA.name(), key.getType());
|
||||
assertEquals(priority, key.getProviderPriority());
|
||||
assertEquals(4096, ((RSAPublicKey) PemUtils.decodePublicKey(keys.getKeys().get(0).getPublicKey())).getModulus().bitLength());
|
||||
}
|
||||
|
@ -177,7 +177,7 @@ public class RsaGeneratedKeyProviderTest extends AbstractKeycloakTest {
|
|||
rep.getConfig().putSingle("keySize", "1234");
|
||||
|
||||
Response response = adminClient.realm("test").components().add(rep);
|
||||
assertErrror(response, "Keysize should be 1024, 2048 or 4096");
|
||||
assertErrror(response, "'Key size' should be 1024, 2048 or 4096");
|
||||
}
|
||||
|
||||
protected void assertErrror(Response response, String error) {
|
|
@ -24,10 +24,11 @@ import org.keycloak.common.util.CertificateUtils;
|
|||
import org.keycloak.common.util.KeyUtils;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.common.util.PemUtils;
|
||||
import org.keycloak.jose.jws.AlgorithmType;
|
||||
import org.keycloak.keys.Attributes;
|
||||
import org.keycloak.keys.ImportedRsaKeyProviderFactory;
|
||||
import org.keycloak.keys.KeyMetadata;
|
||||
import org.keycloak.keys.KeyProvider;
|
||||
import org.keycloak.keys.RsaKeyProviderFactory;
|
||||
import org.keycloak.representations.idm.ComponentRepresentation;
|
||||
import org.keycloak.representations.idm.ErrorRepresentation;
|
||||
import org.keycloak.representations.idm.KeysMetadataRepresentation;
|
||||
|
@ -49,7 +50,7 @@ import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
|
|||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class RsaKeyProviderTest extends AbstractKeycloakTest {
|
||||
public class ImportedRsaKeyProviderTest extends AbstractKeycloakTest {
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(this);
|
||||
|
@ -73,7 +74,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest {
|
|||
KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
|
||||
String kid = KeyUtils.createKeyId(keyPair.getPublic());
|
||||
|
||||
ComponentRepresentation rep = createRep("valid", RsaKeyProviderFactory.ID);
|
||||
ComponentRepresentation rep = createRep("valid", ImportedRsaKeyProviderFactory.ID);
|
||||
rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate()));
|
||||
rep.getConfig().putSingle(Attributes.PRIORITY_KEY, Long.toString(priority));
|
||||
|
||||
|
@ -88,12 +89,12 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest {
|
|||
|
||||
KeysMetadataRepresentation keys = adminClient.realm("test").keys().getKeyMetadata();
|
||||
|
||||
assertEquals(kid, keys.getActive().get(KeyMetadata.Type.RSA.name()));
|
||||
assertEquals(kid, keys.getActive().get(AlgorithmType.RSA.name()));
|
||||
|
||||
KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0);
|
||||
|
||||
assertEquals(id, key.getProviderId());
|
||||
assertEquals(KeyMetadata.Type.RSA.name(), key.getType());
|
||||
assertEquals(AlgorithmType.RSA.name(), key.getType());
|
||||
assertEquals(priority, key.getProviderPriority());
|
||||
assertEquals(kid, key.getKid());
|
||||
assertEquals(PemUtils.encodeKey(keyPair.getPublic()), keys.getKeys().get(0).getPublicKey());
|
||||
|
@ -108,7 +109,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest {
|
|||
Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, "test");
|
||||
String certificatePem = PemUtils.encodeCertificate(certificate);
|
||||
|
||||
ComponentRepresentation rep = createRep("valid", RsaKeyProviderFactory.ID);
|
||||
ComponentRepresentation rep = createRep("valid", ImportedRsaKeyProviderFactory.ID);
|
||||
rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate()));
|
||||
rep.getConfig().putSingle(Attributes.CERTIFICATE_KEY, certificatePem);
|
||||
rep.getConfig().putSingle(Attributes.PRIORITY_KEY, Long.toString(priority));
|
||||
|
@ -130,7 +131,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest {
|
|||
public void invalidPriority() throws Exception {
|
||||
KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
|
||||
|
||||
ComponentRepresentation rep = createRep("invalid", RsaKeyProviderFactory.ID);
|
||||
ComponentRepresentation rep = createRep("invalid", ImportedRsaKeyProviderFactory.ID);
|
||||
rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate()));
|
||||
rep.getConfig().putSingle(Attributes.PRIORITY_KEY, "invalid");
|
||||
|
||||
|
@ -142,7 +143,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest {
|
|||
public void invalidEnabled() throws Exception {
|
||||
KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
|
||||
|
||||
ComponentRepresentation rep = createRep("invalid", RsaKeyProviderFactory.ID);
|
||||
ComponentRepresentation rep = createRep("invalid", ImportedRsaKeyProviderFactory.ID);
|
||||
rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate()));
|
||||
rep.getConfig().putSingle(Attributes.ENABLED_KEY, "invalid");
|
||||
|
||||
|
@ -154,7 +155,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest {
|
|||
public void invalidActive() throws Exception {
|
||||
KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
|
||||
|
||||
ComponentRepresentation rep = createRep("invalid", RsaKeyProviderFactory.ID);
|
||||
ComponentRepresentation rep = createRep("invalid", ImportedRsaKeyProviderFactory.ID);
|
||||
rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate()));
|
||||
rep.getConfig().putSingle(Attributes.ACTIVE_KEY, "invalid");
|
||||
|
||||
|
@ -166,7 +167,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest {
|
|||
public void invalidPrivateKey() throws Exception {
|
||||
KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
|
||||
|
||||
ComponentRepresentation rep = createRep("invalid", RsaKeyProviderFactory.ID);
|
||||
ComponentRepresentation rep = createRep("invalid", ImportedRsaKeyProviderFactory.ID);
|
||||
|
||||
Response response = adminClient.realm("test").components().add(rep);
|
||||
assertErrror(response, "'Private RSA Key' is required");
|
||||
|
@ -185,7 +186,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest {
|
|||
KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
|
||||
Certificate invalidCertificate = CertificateUtils.generateV1SelfSignedCertificate(KeyUtils.generateRsaKeyPair(2048), "test");
|
||||
|
||||
ComponentRepresentation rep = createRep("invalid", RsaKeyProviderFactory.ID);
|
||||
ComponentRepresentation rep = createRep("invalid", ImportedRsaKeyProviderFactory.ID);
|
||||
rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate()));
|
||||
|
||||
rep.getConfig().putSingle(Attributes.CERTIFICATE_KEY, "nonsense");
|
|
@ -23,6 +23,7 @@ import org.junit.Rule;
|
|||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.jose.jws.AlgorithmType;
|
||||
import org.keycloak.keys.JavaKeystoreKeyProviderFactory;
|
||||
import org.keycloak.keys.KeyMetadata;
|
||||
import org.keycloak.keys.KeyProvider;
|
||||
|
@ -103,7 +104,7 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
|
|||
KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0);
|
||||
|
||||
assertEquals(id, key.getProviderId());
|
||||
assertEquals(KeyMetadata.Type.RSA.name(), key.getType());
|
||||
assertEquals(AlgorithmType.RSA.name(), key.getType());
|
||||
assertEquals(priority, key.getProviderPriority());
|
||||
assertEquals(PUBLIC_KEY, key.getPublicKey());
|
||||
assertEquals(CERTIFICATE, key.getCertificate());
|
||||
|
|
|
@ -28,14 +28,13 @@ import org.keycloak.common.util.KeyUtils;
|
|||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.common.util.PemUtils;
|
||||
import org.keycloak.keys.Attributes;
|
||||
import org.keycloak.keys.GeneratedHmacKeyProviderFactory;
|
||||
import org.keycloak.keys.KeyProvider;
|
||||
import org.keycloak.keys.RsaKeyProviderFactory;
|
||||
import org.keycloak.representations.UserInfo;
|
||||
import org.keycloak.keys.ImportedRsaKeyProviderFactory;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.ComponentRepresentation;
|
||||
import org.keycloak.representations.idm.KeysMetadataRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
|
@ -237,7 +236,7 @@ public class KeyRotationTest extends AbstractKeycloakTest {
|
|||
ComponentRepresentation rep = new ComponentRepresentation();
|
||||
rep.setName("mycomponent");
|
||||
rep.setParentId("test");
|
||||
rep.setProviderId(RsaKeyProviderFactory.ID);
|
||||
rep.setProviderId(ImportedRsaKeyProviderFactory.ID);
|
||||
rep.setProviderType(KeyProvider.class.getName());
|
||||
|
||||
org.keycloak.common.util.MultivaluedHashMap config = new org.keycloak.common.util.MultivaluedHashMap();
|
||||
|
@ -247,6 +246,18 @@ public class KeyRotationTest extends AbstractKeycloakTest {
|
|||
|
||||
adminClient.realm("test").components().add(rep);
|
||||
|
||||
rep = new ComponentRepresentation();
|
||||
rep.setName("mycomponent2");
|
||||
rep.setParentId("test");
|
||||
rep.setProviderId(GeneratedHmacKeyProviderFactory.ID);
|
||||
rep.setProviderType(KeyProvider.class.getName());
|
||||
|
||||
config = new org.keycloak.common.util.MultivaluedHashMap();
|
||||
config.addFirst("priority", priority);
|
||||
rep.setConfig(config);
|
||||
|
||||
adminClient.realm("test").components().add(rep);
|
||||
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
|
@ -259,13 +270,16 @@ public class KeyRotationTest extends AbstractKeycloakTest {
|
|||
}
|
||||
|
||||
private void dropKeys(String priority) {
|
||||
int r = 0;
|
||||
for (ComponentRepresentation c : adminClient.realm("test").components().query("test", KeyProvider.class.getName())) {
|
||||
if (c.getConfig().getFirst("priority").equals(priority)) {
|
||||
adminClient.realm("test").components().component(c.getId()).remove();
|
||||
return;
|
||||
r++;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Failed to find keys1");
|
||||
if (r != 2) {
|
||||
throw new RuntimeException("Failed to find keys1");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertUserInfo(String token, int expectedStatus) {
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.model;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.common.util.KeyUtils;
|
||||
import org.keycloak.jose.jws.JWSBuilder;
|
||||
import org.keycloak.protocol.RestartLoginCookie;
|
||||
import org.keycloak.testsuite.rule.WebResource;
|
||||
import org.keycloak.testsuite.rule.WebRule;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import java.security.KeyPair;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
@Ignore
|
||||
public class SimplePerfTest {
|
||||
|
||||
@Rule
|
||||
public WebRule webRule = new WebRule(this);
|
||||
|
||||
@WebResource
|
||||
public WebDriver driver;
|
||||
|
||||
public static final String PORT = "8080";
|
||||
public static final int WARMUP = 1000;
|
||||
public static final int COUNT = 10000;
|
||||
|
||||
@Test
|
||||
public void simplePerf() {
|
||||
// Warm-up
|
||||
for (int i = 0; i < WARMUP; i++) {
|
||||
doLoginLogout(PORT);
|
||||
}
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
long s = start;
|
||||
|
||||
for (int i = 0; i < COUNT; i++) {
|
||||
doLoginLogout(PORT);
|
||||
|
||||
if (i % 100 == 0) {
|
||||
System.out.println(i + " " + (System.currentTimeMillis() - s) + " ms");
|
||||
}
|
||||
s = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
System.out.println("");
|
||||
System.out.println("Average: " + ((System.currentTimeMillis() - start) / COUNT) + " ms");
|
||||
System.out.println("Total: " + ((System.currentTimeMillis() - start)) + " ms");
|
||||
|
||||
}
|
||||
|
||||
private void doLoginLogout(String port) {
|
||||
driver.navigate().to("http://localhost:" + port + "/auth/realms/master/account/");
|
||||
|
||||
driver.findElement(By.id("username")).sendKeys("admin");
|
||||
driver.findElement(By.id("password")).sendKeys("admin");
|
||||
driver.findElement(By.name("login")).click();
|
||||
|
||||
assertEquals("http://localhost:" + port + "/auth/realms/master/account/", driver.getCurrentUrl());
|
||||
|
||||
driver.findElement(By.linkText("Sign Out")).click();
|
||||
}
|
||||
|
||||
}
|
|
@ -39,6 +39,7 @@ sslRequired.option.all=all requests
|
|||
sslRequired.option.external=external requests
|
||||
sslRequired.option.none=none
|
||||
sslRequired.tooltip=Is HTTPS required? 'None' means HTTPS is not required for any client IP address. 'External requests' means localhost and private IP addresses can access without HTTPS. 'All requests' means HTTPS is required for all IP addresses.
|
||||
publicKeys=Public keys
|
||||
publicKey=Public key
|
||||
privateKey=Private key
|
||||
gen-new-keys=Generate new keys
|
||||
|
|
|
@ -1126,6 +1126,14 @@ module.controller('RealmKeysProvidersCtrl', function($scope, Realm, realm, $http
|
|||
type: 'org.keycloak.keys.KeyProvider'
|
||||
}, function(data) {
|
||||
$scope.instances = data;
|
||||
|
||||
for (var i = 0; i < $scope.instances.length; i++) {
|
||||
for (var j = 0; j < $scope.providers.length; j++) {
|
||||
if ($scope.providers[j].id === $scope.instances[i].providerId) {
|
||||
$scope.instances[i].provider = $scope.providers[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$scope.addProvider = function(provider) {
|
||||
|
|
|
@ -27,28 +27,26 @@
|
|||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{:: 'status' | translate}}</th>
|
||||
<th>{{:: 'type' | translate}}</th>
|
||||
<th>{{:: 'status' | translate}}</th>
|
||||
<th>{{:: 'kid' | translate}}</th>
|
||||
<th>{{:: 'priority' | translate}}</th>
|
||||
<th>{{:: 'provider' | translate}}</th>
|
||||
<th>{{:: 'publicKey' | translate}}</th>
|
||||
<th>{{:: 'certificate' | translate}}</th>
|
||||
<th colspan="2">{{:: 'publicKeys' | translate}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="key in keys">
|
||||
<td>{{key.status}}</td>
|
||||
<td>{{key.type}}</td>
|
||||
<td>{{key.status}}</td>
|
||||
<td>{{key.kid}}</td>
|
||||
<td>{{key.providerPriority}}</td>
|
||||
<td><a href="#/realms/{{realm.realm}}/keys/providers/{{key.provider.providerId}}/{{key.provider.id}}">{{key.provider.name}}</a></td>
|
||||
|
||||
<td data-ng-show="key.publicKey" class="kc-action-cell" data-ng-click="viewKey(key.publicKey)">{{:: 'view' | translate}}</td>
|
||||
<td data-ng-hide="key.publicKey"></td>
|
||||
<td data-ng-show="key.type === 'RSA'" class="kc-action-cell" data-ng-click="viewKey(key.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.certificate" class="kc-action-cell" data-ng-click="viewKey(key.certificate)">{{:: 'view' | translate}}</td>
|
||||
<td data-ng-hide="key.certificate"></td>
|
||||
<td data-ng-show="key.type !== 'RSA'" colspan="2"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr ng-show="providers.length > 0 && access.manageRealm">
|
||||
<th colspan="6" class="kc-table-actions">
|
||||
<th colspan="7" class="kc-table-actions">
|
||||
<div class="pull-right">
|
||||
<div>
|
||||
<select class="form-control" ng-model="selectedProvider"
|
||||
|
@ -40,6 +40,7 @@
|
|||
</th>
|
||||
</tr>
|
||||
<tr data-ng-show="instances && instances.length > 0">
|
||||
<th>{{:: 'type' | translate}}</th>
|
||||
<th>{{:: 'name' | translate}}</th>
|
||||
<th>{{:: 'id' | translate}}</th>
|
||||
<th>{{:: 'provider' | translate}}</th>
|
||||
|
@ -49,6 +50,7 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="instance in instances">
|
||||
<td>{{instance.provider.metadata.algorithmType}}</td>
|
||||
<td>{{instance.name}}</td>
|
||||
<td><a href="#/realms/{{realm.realm}}/keys/providers/{{instance.providerId}}/{{instance.id}}">{{instance.id}}</a></td>
|
||||
<td>{{instance.providerId}}</td>
|
||||
|
|
|
@ -13,8 +13,7 @@
|
|||
<th>{{:: 'type' | translate}}</th>
|
||||
<th>{{:: 'kid' | translate}}</th>
|
||||
<th>{{:: 'provider' | translate}}</th>
|
||||
<th>{{:: 'publicKey' | translate}}</th>
|
||||
<th>{{:: 'certificate' | translate}}</th>
|
||||
<th colspan="2">{{:: 'publicKeys' | translate}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -23,11 +22,10 @@
|
|||
<td>{{key.kid}}</td>
|
||||
<td><a href="#/realms/{{realm.realm}}/keys/providers/{{key.provider.providerId}}/{{key.provider.id}}">{{key.provider.name}}</a></td>
|
||||
|
||||
<td data-ng-show="key.publicKey" class="kc-action-cell" data-ng-click="viewKey(key.publicKey)">{{:: 'view' | translate}}</td>
|
||||
<td data-ng-hide="key.publicKey"></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.certificate" class="kc-action-cell" data-ng-click="viewKey(key.certificate)">{{:: 'view' | translate}}</td>
|
||||
<td data-ng-hide="key.certificate"></td>
|
||||
<td data-ng-show="key.type !== 'RSA'" colspan="2"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
Loading…
Reference in a new issue