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