More flexibility in keystore related tests, Make keycloak to notify which keystore types it supports, Support for BCFKS

Closes #14964
This commit is contained in:
mposolda 2022-10-19 11:13:29 +02:00 committed by Marek Posolda
parent 5ebb6e9c10
commit 55c514ad56
21 changed files with 635 additions and 152 deletions

View file

@ -319,7 +319,8 @@ jobs:
run: | run: |
declare -A PARAMS TESTGROUP declare -A PARAMS TESTGROUP
PARAMS["bcfips-nonapproved-pkcs12"]="-Pauth-server-quarkus,auth-server-fips140-2" PARAMS["bcfips-nonapproved-pkcs12"]="-Pauth-server-quarkus,auth-server-fips140-2"
TESTGROUP["group1"]="-Dtest=org.keycloak.testsuite.forms.**" # Tests in the package "forms" # Tests in the package "forms" and some keystore related tests
TESTGROUP["group1"]="-Dtest=org.keycloak.testsuite.forms.**,ClientAuthSignedJWTTest,CredentialsTest,JavaKeystoreKeyProviderTest,ServerInfoTest"
./mvnw clean install -nsu -B ${PARAMS["${{ matrix.server }}"]} ${TESTGROUP["${{ matrix.tests }}"]} -f testsuite/integration-arquillian/tests/base/pom.xml | misc/log/trimmer.sh ./mvnw clean install -nsu -B ${PARAMS["${{ matrix.server }}"]} ${TESTGROUP["${{ matrix.tests }}"]} -f testsuite/integration-arquillian/tests/base/pom.xml | misc/log/trimmer.sh

View file

@ -15,6 +15,7 @@ import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.security.cert.CollectionCertStoreParameters; import java.security.cert.CollectionCertStoreParameters;
import java.security.spec.ECParameterSpec; import java.security.spec.ECParameterSpec;
import java.util.stream.Stream;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException; import javax.crypto.NoSuchPaddingException;
@ -89,6 +90,21 @@ public interface CryptoProvider {
KeyStore getKeyStore(KeystoreFormat format) throws KeyStoreException, NoSuchProviderException; KeyStore getKeyStore(KeystoreFormat format) throws KeyStoreException, NoSuchProviderException;
/**
* @return Keystore types/algorithms supported by this CryptoProvider
*/
default Stream<KeystoreFormat> getSupportedKeyStoreTypes() {
return Stream.of(KeystoreFormat.values())
.filter(format -> {
try {
getKeyStore(format);
return true;
} catch (KeyStoreException | NoSuchProviderException ex) {
return false;
}
});
}
CertificateFactory getX509CertFactory() throws CertificateException, NoSuchProviderException; CertificateFactory getX509CertFactory() throws CertificateException, NoSuchProviderException;
CertStore getCertStore(CollectionCertStoreParameters collectionCertStoreParameters) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException; CertStore getCertStore(CollectionCertStoreParameters collectionCertStoreParameters) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException;

View file

@ -27,6 +27,8 @@ import java.security.KeyPair;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.util.Arrays;
import java.util.Optional;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -35,12 +37,24 @@ import java.security.PublicKey;
public class KeystoreUtil { public class KeystoreUtil {
public enum KeystoreFormat { public enum KeystoreFormat {
JKS, JKS("jks"),
PKCS12 PKCS12("p12"),
BCFKS("bcfks");
// Typical file extension for this keystore format
private final String fileExtension;
KeystoreFormat(String extension) {
this.fileExtension = extension;
}
public String getFileExtension() {
return fileExtension;
}
} }
public static KeyStore loadKeyStore(String filename, String password) throws Exception { public static KeyStore loadKeyStore(String filename, String password) throws Exception {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); String keystoreType = getKeystoreType(null, filename, KeyStore.getDefaultType());
KeyStore trustStore = KeyStore.getInstance(keystoreType);
InputStream trustStream = null; InputStream trustStream = null;
if (filename.startsWith(GenericConstants.PROTOCOL_CLASSPATH)) { if (filename.startsWith(GenericConstants.PROTOCOL_CLASSPATH)) {
String resourcePath = filename.replace(GenericConstants.PROTOCOL_CLASSPATH, ""); String resourcePath = filename.replace(GenericConstants.PROTOCOL_CLASSPATH, "");
@ -79,4 +93,31 @@ public class KeystoreUtil {
throw new RuntimeException("Failed to load private key: " + e.getMessage(), e); throw new RuntimeException("Failed to load private key: " + e.getMessage(), e);
} }
} }
/**
* Try to return supported keystore type
*
* @param preferredType The preferred format - usually the one from the configuration. When present, it should be preferred over anything else
* @param path Path of the file. We can try to detect keystore type from that (EG. my-keystore.pkcs12 will return "pkcs12") in case that preferredType is not defined
* @param defaultType Default format as last fallback when none of the above can be used. Should be non-null
* @return format as specified above
*/
public static String getKeystoreType(String preferredType, String path, String defaultType) {
// Configured type has precedence
if (preferredType != null) return preferredType;
// Fallback to path
int lastDotIndex = path.lastIndexOf('.');
if (lastDotIndex > -1) {
String ext = path.substring(lastDotIndex + 1).toLowerCase();
Optional<KeystoreFormat> detectedType = Arrays.stream(KeystoreUtil.KeystoreFormat.values())
.filter(ksFormat -> ksFormat.getFileExtension().equals(ext))
.findFirst();
if (detectedType.isPresent()) return detectedType.get().toString();
}
// Fallback to default
return defaultType;
}
} }

View file

@ -0,0 +1,64 @@
/*
* Copyright 2022 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.representations.info;
import java.util.List;
import java.util.stream.Collectors;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.crypto.CryptoProvider;
import org.keycloak.common.util.KeystoreUtil;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class CryptoInfoRepresentation {
private String cryptoProvider;
private List<String> supportedKeystoreTypes;
public static CryptoInfoRepresentation create() {
CryptoInfoRepresentation info = new CryptoInfoRepresentation();
CryptoProvider cryptoProvider = CryptoIntegration.getProvider();
info.cryptoProvider = cryptoProvider.getClass().getSimpleName();
info.supportedKeystoreTypes = CryptoIntegration.getProvider().getSupportedKeyStoreTypes()
.map(KeystoreUtil.KeystoreFormat::toString)
.collect(Collectors.toList());
return info;
}
public String getCryptoProvider() {
return cryptoProvider;
}
public void setCryptoProvider(String cryptoProvider) {
this.cryptoProvider = cryptoProvider;
}
public List<String> getSupportedKeystoreTypes() {
return supportedKeystoreTypes;
}
public void setSupportedKeystoreTypes(List<String> supportedKeystoreTypes) {
this.supportedKeystoreTypes = supportedKeystoreTypes;
}
}

View file

@ -34,6 +34,8 @@ public class ServerInfoRepresentation {
private MemoryInfoRepresentation memoryInfo; private MemoryInfoRepresentation memoryInfo;
private ProfileInfoRepresentation profileInfo; private ProfileInfoRepresentation profileInfo;
private CryptoInfoRepresentation cryptoInfo;
private Map<String, List<ThemeInfoRepresentation>> themes; private Map<String, List<ThemeInfoRepresentation>> themes;
private List<Map<String, String>> socialProviders; private List<Map<String, String>> socialProviders;
@ -75,6 +77,14 @@ public class ServerInfoRepresentation {
this.profileInfo = profileInfo; this.profileInfo = profileInfo;
} }
public CryptoInfoRepresentation getCryptoInfo() {
return cryptoInfo;
}
public void setCryptoInfo(CryptoInfoRepresentation cryptoInfo) {
this.cryptoInfo = cryptoInfo;
}
public Map<String, List<ThemeInfoRepresentation>> getThemes() { public Map<String, List<ThemeInfoRepresentation>> getThemes() {
return themes; return themes;
} }

View file

@ -0,0 +1,41 @@
package org.keycloak.crypto.def.test;
import java.util.Set;
import java.util.stream.Collectors;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.rule.CryptoInitRule;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class DefaultKeyStoreTypesTest {
@ClassRule
public static CryptoInitRule cryptoInitRule = new CryptoInitRule();
@Test
public void testKeystoreFormats() {
Set<KeystoreUtil.KeystoreFormat> supportedKeystoreFormats = CryptoIntegration.getProvider().getSupportedKeyStoreTypes().collect(Collectors.toSet());
Assert.assertThat(supportedKeystoreFormats, Matchers.containsInAnyOrder(
KeystoreUtil.KeystoreFormat.JKS,
KeystoreUtil.KeystoreFormat.PKCS12,
KeystoreUtil.KeystoreFormat.BCFKS));
}
@Test
public void testDefaultKeystoreType() {
Assert.assertEquals("PKCS12", KeystoreUtil.getKeystoreType("PKCS12", "some/foo.jks", "JKS"));
Assert.assertEquals("PKCS12", KeystoreUtil.getKeystoreType("PKCS12", "some/foo.pkcs12", "JKS"));
Assert.assertEquals("PKCS12", KeystoreUtil.getKeystoreType("PKCS12", "some/foo.bcfks", "JKS"));
Assert.assertEquals("JKS", KeystoreUtil.getKeystoreType(null, "some/foo.jks", "JKS"));
Assert.assertEquals("PKCS12", KeystoreUtil.getKeystoreType(null, "some/foo.p12", "JKS"));
Assert.assertEquals("BCFKS", KeystoreUtil.getKeystoreType(null, "some/foo.bcfks", "JKS"));
Assert.assertEquals("JKS", KeystoreUtil.getKeystoreType(null, "some/foo.bcfksl", "JKS"));
}
}

View file

@ -66,6 +66,11 @@
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>

View file

@ -0,0 +1,50 @@
/*
* Copyright 2022 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.crypto.elytron.test;
import java.util.Set;
import java.util.stream.Collectors;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.rule.CryptoInitRule;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ElytronKeyStoreTypesTest {
@ClassRule
public static CryptoInitRule cryptoInitRule = new CryptoInitRule();
// No BCFKS keystore type supported for elytron
@Test
public void testKeystoreFormats() {
Set<KeystoreUtil.KeystoreFormat> supportedKeystoreFormats = CryptoIntegration.getProvider().getSupportedKeyStoreTypes().collect(Collectors.toSet());
Assert.assertThat(supportedKeystoreFormats, Matchers.containsInAnyOrder(
KeystoreUtil.KeystoreFormat.JKS,
KeystoreUtil.KeystoreFormat.PKCS12
));
}
}

View file

@ -0,0 +1,49 @@
package org.keycloak.crypto.fips.test;
import java.util.Set;
import java.util.stream.Collectors;
import org.bouncycastle.crypto.CryptoServicesRegistrar;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.util.Environment;
import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.rule.CryptoInitRule;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class FIPS1402KeystoreTypesTest {
@ClassRule
public static CryptoInitRule cryptoInitRule = new CryptoInitRule();
@Before
public void before() {
// Run this test just if java is in FIPS mode
Assume.assumeTrue("Java is not in FIPS mode. Skipping the test.", Environment.isJavaInFipsMode());
}
@Test
public void testKeystoreFormatsInNonApprovedMode() {
Assume.assumeFalse(CryptoServicesRegistrar.isInApprovedOnlyMode());
Set<KeystoreUtil.KeystoreFormat> supportedKeystoreFormats = CryptoIntegration.getProvider().getSupportedKeyStoreTypes().collect(Collectors.toSet());
Assert.assertThat(supportedKeystoreFormats, Matchers.containsInAnyOrder(
KeystoreUtil.KeystoreFormat.PKCS12,
KeystoreUtil.KeystoreFormat.BCFKS));
}
// BCFIPS approved mode supports only BCFKS. No JKS nor PKCS12 support for keystores
@Test
public void testKeystoreFormatsInApprovedMode() {
Assume.assumeTrue(CryptoServicesRegistrar.isInApprovedOnlyMode());
Set<KeystoreUtil.KeystoreFormat> supportedKeystoreFormats = CryptoIntegration.getProvider().getSupportedKeyStoreTypes().collect(Collectors.toSet());
Assert.assertThat(supportedKeystoreFormats, Matchers.containsInAnyOrder(
KeystoreUtil.KeystoreFormat.BCFKS));
}
}

View file

@ -19,6 +19,7 @@ package org.keycloak.keys;
import org.keycloak.common.util.CertificateUtils; import org.keycloak.common.util.CertificateUtils;
import org.keycloak.common.util.KeyUtils; import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.KeyUse; import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper; import org.keycloak.crypto.KeyWrapper;
@ -61,8 +62,11 @@ public class JavaKeystoreKeyProvider extends AbstractRsaKeyProvider {
@Override @Override
protected KeyWrapper loadKey(RealmModel realm, ComponentModel model) { protected KeyWrapper loadKey(RealmModel realm, ComponentModel model) {
try (FileInputStream is = new FileInputStream(model.get(JavaKeystoreKeyProviderFactory.KEYSTORE_KEY))) { String keystorePath = model.get(JavaKeystoreKeyProviderFactory.KEYSTORE_KEY);
KeyStore keyStore = KeyStore.getInstance("JKS"); try (FileInputStream is = new FileInputStream(keystorePath)) {
// Use "JKS" as default type for backwards compatibility
String keystoreType = KeystoreUtil.getKeystoreType(model.get(JavaKeystoreKeyProviderFactory.KEYSTORE_TYPE_KEY), keystorePath, "JKS");
KeyStore keyStore = KeyStore.getInstance(keystoreType);
keyStore.load(is, model.get(JavaKeystoreKeyProviderFactory.KEYSTORE_PASSWORD_KEY).toCharArray()); keyStore.load(is, model.get(JavaKeystoreKeyProviderFactory.KEYSTORE_PASSWORD_KEY).toCharArray());
String keyAlias = model.get(JavaKeystoreKeyProviderFactory.KEY_ALIAS_KEY); String keyAlias = model.get(JavaKeystoreKeyProviderFactory.KEY_ALIAS_KEY);

View file

@ -18,6 +18,9 @@
package org.keycloak.keys; package org.keycloak.keys;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException; import org.keycloak.component.ComponentValidationException;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
@ -27,6 +30,7 @@ import org.keycloak.provider.ProviderConfigProperty;
import java.util.List; import java.util.List;
import static org.keycloak.provider.ProviderConfigProperty.LIST_TYPE;
import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE; import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
/** /**
@ -43,6 +47,11 @@ public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactor
public static String KEYSTORE_PASSWORD_KEY = "keystorePassword"; public static String KEYSTORE_PASSWORD_KEY = "keystorePassword";
public static ProviderConfigProperty KEYSTORE_PASSWORD_PROPERTY = new ProviderConfigProperty(KEYSTORE_PASSWORD_KEY, "Keystore Password", "Password for the keys", STRING_TYPE, null, true); public static ProviderConfigProperty KEYSTORE_PASSWORD_PROPERTY = new ProviderConfigProperty(KEYSTORE_PASSWORD_KEY, "Keystore Password", "Password for the keys", STRING_TYPE, null, true);
public static String KEYSTORE_TYPE_KEY = "keystoreType";
// Initialization of this property is postponed to "init()" due the CryptoProvider must be set
private ProviderConfigProperty keystoreTypeProperty;
public static String KEY_ALIAS_KEY = "keyAlias"; public static String KEY_ALIAS_KEY = "keyAlias";
public static ProviderConfigProperty KEY_ALIAS_PROPERTY = new ProviderConfigProperty(KEY_ALIAS_KEY, "Key Alias", "Alias for the private key", STRING_TYPE, null); public static ProviderConfigProperty KEY_ALIAS_PROPERTY = new ProviderConfigProperty(KEY_ALIAS_KEY, "Key Alias", "Alias for the private key", STRING_TYPE, null);
@ -51,13 +60,26 @@ public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactor
private static final String HELP_TEXT = "Loads keys from a Java keys file"; private static final String HELP_TEXT = "Loads keys from a Java keys file";
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = AbstractRsaKeyProviderFactory.configurationBuilder() private List<ProviderConfigProperty> configProperties;
.property(KEYSTORE_PROPERTY)
.property(KEYSTORE_PASSWORD_PROPERTY) @Override
.property(KEY_ALIAS_PROPERTY) public void init(Config.Scope config) {
.property(KEY_PASSWORD_PROPERTY) String[] supportedKeystoreTypes = CryptoIntegration.getProvider().getSupportedKeyStoreTypes()
.property(Attributes.KEY_USE_PROPERTY) .map(KeystoreUtil.KeystoreFormat::toString)
.build(); .toArray(String[]::new);
this.keystoreTypeProperty = new ProviderConfigProperty(KEYSTORE_TYPE_KEY, "Keystore Type",
"Keystore type. This parameter is not mandatory. If omitted, the type will be detected from keystore file or default keystore type will be used", LIST_TYPE,
supportedKeystoreTypes.length > 0 ? supportedKeystoreTypes[0] : null, supportedKeystoreTypes);
configProperties = AbstractRsaKeyProviderFactory.configurationBuilder()
.property(KEYSTORE_PROPERTY)
.property(KEYSTORE_PASSWORD_PROPERTY)
.property(keystoreTypeProperty)
.property(KEY_ALIAS_PROPERTY)
.property(KEY_PASSWORD_PROPERTY)
.property(Attributes.KEY_USE_PROPERTY)
.build();
}
@Override @Override
public KeyProvider create(KeycloakSession session, ComponentModel model) { public KeyProvider create(KeycloakSession session, ComponentModel model) {
@ -71,6 +93,7 @@ public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactor
ConfigurationValidationHelper.check(model) ConfigurationValidationHelper.check(model)
.checkSingle(KEYSTORE_PROPERTY, true) .checkSingle(KEYSTORE_PROPERTY, true)
.checkSingle(KEYSTORE_PASSWORD_PROPERTY, true) .checkSingle(KEYSTORE_PASSWORD_PROPERTY, true)
.checkSingle(keystoreTypeProperty, false)
.checkSingle(KEY_ALIAS_PROPERTY, true) .checkSingle(KEY_ALIAS_PROPERTY, true)
.checkSingle(KEY_PASSWORD_PROPERTY, true); .checkSingle(KEY_PASSWORD_PROPERTY, true);
@ -89,7 +112,7 @@ public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactor
@Override @Override
public List<ProviderConfigProperty> getConfigProperties() { public List<ProviderConfigProperty> getConfigProperties() {
return CONFIG_PROPERTIES; return this.configProperties;
} }
@Override @Override

View file

@ -63,6 +63,8 @@ import java.security.cert.Certificate;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/** /**
* @resource Client Attribute Certificate * @resource Client Attribute Certificate
@ -268,9 +270,7 @@ public class ClientAttributeCertificateResource {
public byte[] getKeystore(final KeyStoreConfig config) { public byte[] getKeystore(final KeyStoreConfig config) {
auth.clients().requireView(client); auth.clients().requireView(client);
if (config.getFormat() != null && !config.getFormat().equals("JKS") && !config.getFormat().equals("PKCS12")) { checkKeystoreFormat(config);
throw new NotAcceptableException("Only support jks or pkcs12 format.");
}
CertificateRepresentation info = CertificateInfoHelper.getCertificateFromClient(client, attributePrefix); CertificateRepresentation info = CertificateInfoHelper.getCertificateFromClient(client, attributePrefix);
String privatePem = info.getPrivateKey(); String privatePem = info.getPrivateKey();
@ -307,9 +307,7 @@ public class ClientAttributeCertificateResource {
public byte[] generateAndGetKeystore(final KeyStoreConfig config) { public byte[] generateAndGetKeystore(final KeyStoreConfig config) {
auth.clients().requireConfigure(client); auth.clients().requireConfigure(client);
if (config.getFormat() != null && !config.getFormat().equals("JKS") && !config.getFormat().equals("PKCS12")) { checkKeystoreFormat(config);
throw new NotAcceptableException("Only support jks or pkcs12 format.");
}
if (config.getKeyPassword() == null) { if (config.getKeyPassword() == null) {
throw new ErrorResponseException("password-missing", "Need to specify a key password for jks generation and download", Response.Status.BAD_REQUEST); throw new ErrorResponseException("password-missing", "Need to specify a key password for jks generation and download", Response.Status.BAD_REQUEST);
} }
@ -369,5 +367,20 @@ public class ClientAttributeCertificateResource {
} }
} }
private void checkKeystoreFormat(KeyStoreConfig config) throws NotAcceptableException {
if (config.getFormat() != null) {
Set<KeystoreFormat> supportedKeystoreFormats = CryptoIntegration.getProvider().getSupportedKeyStoreTypes()
.collect(Collectors.toSet());
try {
KeystoreFormat format = Enum.valueOf(KeystoreFormat.class, config.getFormat().toUpperCase());
if (config.getFormat() != null && !supportedKeystoreFormats.contains(format)) {
throw new NotAcceptableException("Not supported keystore format. Supported keystore formats: " + supportedKeystoreFormats);
}
} catch (IllegalArgumentException iae) {
throw new NotAcceptableException("Not supported keystore format. Supported keystore formats: " + supportedKeystoreFormats);
}
}
}
} }

View file

@ -44,6 +44,7 @@ import org.keycloak.representations.idm.PasswordPolicyTypeRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.ProtocolMapperTypeRepresentation; import org.keycloak.representations.idm.ProtocolMapperTypeRepresentation;
import org.keycloak.representations.info.ClientInstallationRepresentation; import org.keycloak.representations.info.ClientInstallationRepresentation;
import org.keycloak.representations.info.CryptoInfoRepresentation;
import org.keycloak.representations.info.MemoryInfoRepresentation; import org.keycloak.representations.info.MemoryInfoRepresentation;
import org.keycloak.representations.info.ProfileInfoRepresentation; import org.keycloak.representations.info.ProfileInfoRepresentation;
import org.keycloak.representations.info.ProviderRepresentation; import org.keycloak.representations.info.ProviderRepresentation;
@ -93,6 +94,7 @@ public class ServerInfoAdminResource {
info.setSystemInfo(SystemInfoRepresentation.create(session.getKeycloakSessionFactory().getServerStartupTimestamp())); info.setSystemInfo(SystemInfoRepresentation.create(session.getKeycloakSessionFactory().getServerStartupTimestamp()));
info.setMemoryInfo(MemoryInfoRepresentation.create()); info.setMemoryInfo(MemoryInfoRepresentation.create());
info.setProfileInfo(ProfileInfoRepresentation.create()); info.setProfileInfo(ProfileInfoRepresentation.create());
info.setCryptoInfo(CryptoInfoRepresentation.create());
setSocialProviders(info); setSocialProviders(info);
setIdentityProviders(info); setIdentityProviders(info);

View file

@ -45,7 +45,6 @@ import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import javax.security.auth.x500.X500Principal; import javax.security.auth.x500.X500Principal;
@ -86,7 +85,7 @@ public class FileTruststoreProviderFactory implements TruststoreProviderFactory
throw new RuntimeException("Attribute 'password' missing in 'truststore':'file' configuration"); throw new RuntimeException("Attribute 'password' missing in 'truststore':'file' configuration");
} }
String type = getTruststoreType(storepath, configuredType); String type = KeystoreUtil.getKeystoreType(configuredType, storepath, KeyStore.getDefaultType());
try { try {
truststore = loadStore(storepath, type, pass == null ? null :pass.toCharArray()); truststore = loadStore(storepath, type, pass == null ? null :pass.toCharArray());
} catch (Exception e) { } catch (Exception e) {
@ -159,25 +158,6 @@ public class FileTruststoreProviderFactory implements TruststoreProviderFactory
.build(); .build();
} }
private String getTruststoreType(String path, String configuredType) {
// Configured type has precedence
if (configuredType != null) return configuredType;
// Fallback to detected tyoe from the file format (EG. my-keystore.pkcs12 will return "pkcs12")
int lastDotIndex = path.lastIndexOf('.');
if (lastDotIndex > -1) {
String ext = path.substring(lastDotIndex).toUpperCase();
Optional<String> detectedType = Arrays.stream(KeystoreUtil.KeystoreFormat.values())
.map(KeystoreUtil.KeystoreFormat::toString)
.filter(ksFormat -> ksFormat.equals(ext))
.findFirst();
if (detectedType.isPresent()) return detectedType.get();
}
// Fallback to default JVM
return KeyStore.getDefaultType();
}
private static class TruststoreCertificatesLoader { private static class TruststoreCertificatesLoader {
private Map<X500Principal, X509Certificate> trustedRootCerts = new HashMap<>(); private Map<X500Principal, X509Certificate> trustedRootCerts = new HashMap<>();

View file

@ -0,0 +1,105 @@
/*
* Copyright 2022 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.util;
import java.io.File;
import java.io.FileOutputStream;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.stream.Stream;
import org.junit.Assume;
import org.junit.rules.TemporaryFolder;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.util.CertificateUtils;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.common.util.PemUtils;
import org.keycloak.representations.idm.CertificateRepresentation;
import static org.junit.Assert.fail;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class KeystoreUtils {
public static String[] getSupportedKeystoreTypes() {
String supportedKeystoreTypes = System.getProperty("auth.server.supported.keystore.types");
if (supportedKeystoreTypes == null || supportedKeystoreTypes.trim().isEmpty()) {
fail("Property 'auth.server.supported.keystore.types' not set");
}
return supportedKeystoreTypes.split(",");
}
public static KeystoreUtil.KeystoreFormat getPreferredKeystoreType() {
return Enum.valueOf(KeystoreUtil.KeystoreFormat.class, getSupportedKeystoreTypes()[0]);
}
public static void assumeKeystoreTypeSupported(KeystoreUtil.KeystoreFormat keystoreType) {
String[] supportedKeystoreTypes = KeystoreUtils.getSupportedKeystoreTypes();
Assume.assumeTrue("Keystore type '" + keystoreType + "' not supported. Supported keystore types: " + Arrays.asList(supportedKeystoreTypes),
Stream.of(supportedKeystoreTypes)
.anyMatch(type -> type.equals(keystoreType.toString())));
}
public static KeystoreInfo generateKeystore(TemporaryFolder folder, KeystoreUtil.KeystoreFormat keystoreType, String subject, String keystorePassword, String keyPassword) throws Exception {
String fileName = "keystore." + keystoreType.getFileExtension();
KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
X509Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, subject);
KeyStore keyStore = CryptoIntegration.getProvider().getKeyStore(keystoreType);
keyStore.load(null, null);
Certificate[] chain = {certificate};
keyStore.setKeyEntry(subject, keyPair.getPrivate(), keyPassword.trim().toCharArray(), chain);
File file = folder.newFile(fileName);
keyStore.store(new FileOutputStream(file), keystorePassword.trim().toCharArray());
CertificateRepresentation certRep = new CertificateRepresentation();
certRep.setPrivateKey(PemUtils.encodeKey(keyPair.getPrivate()));
certRep.setPublicKey(PemUtils.encodeKey(keyPair.getPublic()));
certRep.setCertificate(PemUtils.encodeCertificate(certificate));
return new KeystoreInfo(certRep, file);
}
public static class KeystoreInfo {
private final CertificateRepresentation certificateInfo;
private final File keystoreFile;
private KeystoreInfo(CertificateRepresentation certificateInfo, File keystoreFile) {
this.certificateInfo = certificateInfo;
this.keystoreFile = keystoreFile;
}
public CertificateRepresentation getCertificateInfo() {
return certificateInfo;
}
public File getKeystoreFile() {
return keystoreFile;
}
}
}

View file

@ -23,12 +23,14 @@ import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.info.ProviderRepresentation; import org.keycloak.representations.info.ProviderRepresentation;
import org.keycloak.representations.info.ServerInfoRepresentation; import org.keycloak.representations.info.ServerInfoRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.Assert;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -56,6 +58,12 @@ public class ServerInfoTest extends AbstractKeycloakTest {
assertNotNull(info.getMemoryInfo()); assertNotNull(info.getMemoryInfo());
assertNotNull(info.getSystemInfo()); assertNotNull(info.getSystemInfo());
assertNotNull(info.getCryptoInfo());
String expectedSupportedKeystoreTypes = System.getProperty("auth.server.supported.keystore.types");
if (expectedSupportedKeystoreTypes == null) {
fail("Property 'auth.server.supported.keystore.types' not set");
}
Assert.assertNames(info.getCryptoInfo().getSupportedKeystoreTypes(), expectedSupportedKeystoreTypes.split(","));
assertEquals(Version.VERSION, info.getSystemInfo().getVersion()); assertEquals(Version.VERSION, info.getSystemInfo().getVersion());
assertNotNull(info.getSystemInfo().getServerTime()); assertNotNull(info.getSystemInfo().getServerTime());

View file

@ -20,8 +20,11 @@ package org.keycloak.testsuite.admin.client;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput; import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.keycloak.admin.client.resource.ClientAttributeCertificateResource; import org.keycloak.admin.client.resource.ClientAttributeCertificateResource;
import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.common.util.PemUtils; import org.keycloak.common.util.PemUtils;
import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType; import org.keycloak.events.admin.ResourceType;
@ -31,13 +34,12 @@ import org.keycloak.representations.idm.CertificateRepresentation;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.testsuite.util.AdminEventPaths; import org.keycloak.testsuite.util.AdminEventPaths;
import org.keycloak.testsuite.util.KeystoreUtils;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.net.URL; import java.io.FileInputStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.Key; import java.security.Key;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.cert.Certificate; import java.security.cert.Certificate;
@ -108,81 +110,90 @@ public class CredentialsTest extends AbstractClientTest {
@Test @Test
public void testUploadKeyAndCertificate() throws Exception { public void testUploadKeyAndCertificate() throws Exception {
String privateKey = "MIIEowIBAAKCAQEAhSOOC/5Xez3o75lr3TTYun+2u0a4cF5p5Uv10UowrM7Yw+p1GYcHg+o2UN13bxHB/lefqJZ0WnJQo6cj/JcMuF1y4WlHSww0r8L0u36FKk8Uu7MOqC0+AOi2UzGIchYM5nuD3+A9g1ds2+O/ydKLKqiC6gJCKJp9b3Rs8eyJUt0/tkhTAJx+LWpCbsWHFEnU2Jbl29SS4KedYR/RdH5bNzl4L0SAHS1osWI+xIQiVYybnGVqFjJeQ9006pmOJGetNablji6TxlywP8ps9N//u3txBeKlVqzCCN1iLWQrb/NHA6GDVDBYVf+qa91358vFXRHpWpEOGftB6nZzHAzEuwIDAQABAoIBAHb5IsJM8lfLJxCVBPKTeuiNn/kSZVbkx7SDgJMZvQ1vefz40tOQ+oJDFW6FuWijcbubCa1ZZXg9lxnnDh11zYQi3bnYnkDOE3bMvG2fzdfU+y4QABUA+NtPGT6WkNuCIN0Fmv7AH7fys/B7QLNVVc807me2xPALvfOPEpvNR5mnjquCTOfDzbh5U6hGFcuLnZdQbCK2hG5R8DXE2pLvoa0i1cMMgVaWQ5mVSg0N3G0Q5ZF8YJEasAeJUCGlPFgJ4ySfGsKSUcMODQzHmqvLzArJmFgW6Uah0CgqedBTujmzJ6FwfbzGR0wpk08cf5BPzs9Flwka10ITA4h4QzlBnuECgYEA8TFZWq42biHaZmo0NVVEoltIDl1ci5m2xr7yU6TMfrsGKFFiszCPWuKcK5J8Svm0P9H9vlVpCHZ+JVfEGnve1/wVB/6E0lY6cz4uJTV4t4F1QJN5j9nyRrS0i9zDEIRgO4mvD9Zlm/OvHEdTmtVg97cbS4nWvRAPdB2DaZ0w0V8CgYEAjVAAb5Q6Jqb5XT5ZM1Cc6S3PzBAA7GGc3Rqyugxts5WEReRXdNITocj/71c0VZ+qC9+EvV8im/7QPl5NbRiI2p3oPqqV5Brk/MVfDLhu/mkawW0mlPtuBkZIRE0/eXTGN9Dq6yvxo9d6kwka7RW1CBZxi1/M78hKGCHXM7umviUCgYEA4cLvgJHRIQVPCM4gUEugEtieedOp7IHVM/NHoEOBpp4pBVQortGlXcz/oUlcTlGtBo/ok2AfEGzZZtrgFGoeDM1IYlM6wCc2TujFCM8kT6A9wFRKVPwMa2J6HPBnJe7CpPgbhReJxJA0OKQK/cL9IOGkCvDar914mZeGijU4nMECgYAqZL7Muo47fEpBE+xUvbFlLu4xDPgJ8jrKBjFqKUJb5tYY1aj7De7/0Toexm2X5l9wUm0TFtBeNjKpE0dtHDgqRccfzbNMDFl4D4o1WbtKraNuNd2mQku+rCUQAJCzUjoJEq73QGasvX8zTz75s1JtC7ailmn34YGA/d3+0iPy1QKBgHXneWpJVcQ9Lk34DnSLZLK+W1sTK8xLTJSyy3U0F84r+ir8bvsP9EQpZI0Nx3DqvF4/ZHmK2cfSxGSKm4VhZfG0LYCqtSmaHErZJaLJA8xJELkkEKj/ZUqkZ+4zhY7RMwyZtmXcxvaR/pzRZZwbTQ4ueZKKUIsK2AaHTsSCGDMq"; TemporaryFolder folder = new TemporaryFolder();
String certificate = "MIICnTCCAYUCBgFPPLDaTzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE3MjI0N1oXDTI1MDgxNzE3MjQyN1owEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIUjjgv+V3s96O+Za9002Lp/trtGuHBeaeVL9dFKMKzO2MPqdRmHB4PqNlDdd28Rwf5Xn6iWdFpyUKOnI/yXDLhdcuFpR0sMNK/C9Lt+hSpPFLuzDqgtPgDotlMxiHIWDOZ7g9/gPYNXbNvjv8nSiyqoguoCQiiafW90bPHsiVLdP7ZIUwCcfi1qQm7FhxRJ1NiW5dvUkuCnnWEf0XR+Wzc5eC9EgB0taLFiPsSEIlWMm5xlahYyXkPdNOqZjiRnrTWm5Y4uk8ZcsD/KbPTf/7t7cQXipVaswgjdYi1kK2/zRwOhg1QwWFX/qmvdd+fLxV0R6VqRDhn7Qep2cxwMxLsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAKE6OA46sf20bz8LZPoiNsqRwBUDkaMGXfnob7s/hJZIIwDEx0IAQ3uKsG7q9wb+aA6s+v7S340zb2k3IxuhFaHaZpAd4CyR5cn1FHylbzoZ7rI/3ASqHDqpljdJaFqPH+m7nZWtyDvtZf+gkZ8OjsndwsSBK1d/jMZPp29qYbl1+XfO7RCp/jDqro/R3saYFaIFiEZPeKn1hUJn6BO48vxH1xspSu9FmlvDOEAOz4AuM58z4zRMP49GcFdCWr1wkonJUHaSptJaQwmBwLFUkCbE5I1ixGMb7mjEud6Y5jhfzJiZMo2U8RfcjNbrN0diZl3jB6LQIwESnhYSghaTjNQ=="; folder.create();
String certificate2 = "MIICnTCCAYUCBgFPPQDGxTANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE4NTAwNVoXDTI1MDgxNzE4NTE0NVowEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMMw3PaBffWxgS2PYSDDBp6As+cNvv9kt2C4f/RDAGmvSIHPFev9kuQiKs3Oaws3ZsV4JG3qHEuYgnh9W4vfe3DwNwtD1bjL5FYBhPBFTw0lAQECYxaBHnkjHwUKp957FqdSPPICm3LjmTcEdlH+9dpp9xHCMbbiNiWDzWI1xSxC8Fs2d0hwz1sd+Q4QeTBPIBWcPM+ICZtNG5MN+ORfayu4X+Me5d0tXG2fQO//rAevk1i5IFjKZuOjTwyKB5SJIY4b8QTeg0g/50IU7Ht00Pxw6CK02dHS+FvXHasZlD3ckomqCDjStTBWdhJo5dST0CbOqalkkpLlCCbGA1yEQRsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAUIMeJ+EAo8eNpCG/nXImacjrKakbFnZYBGD/gqeTGaZynkX+jgBSructTHR83zSH+yELEhsAy+3BfK4EEihp+PEcRnK2fASVkHste8AQ7rlzC+HGGirlwrVhWCdizNUCGK80DE537IZ7nmZw6LFG9P5/Q2MvCsOCYjRUvMkukq6TdXBXR9tETwZ+0gpSfsOxjj0ZF7ftTRUSzx4rFfcbM9fRNdVizdOuKGc8HJPA5lLOxV6CyaYIvi3y5RlQI1OHeS34lE4w9CNPRFa/vdxXvN7ClyzA0HMFNWxBN7pC/Ht/FbhSvaAagJBHg+vCrcY5C26Oli7lAglf/zZrwUPs0w=="; try {
String certificate2 = "MIICnTCCAYUCBgFPPQDGxTANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE4NTAwNVoXDTI1MDgxNzE4NTE0NVowEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMMw3PaBffWxgS2PYSDDBp6As+cNvv9kt2C4f/RDAGmvSIHPFev9kuQiKs3Oaws3ZsV4JG3qHEuYgnh9W4vfe3DwNwtD1bjL5FYBhPBFTw0lAQECYxaBHnkjHwUKp957FqdSPPICm3LjmTcEdlH+9dpp9xHCMbbiNiWDzWI1xSxC8Fs2d0hwz1sd+Q4QeTBPIBWcPM+ICZtNG5MN+ORfayu4X+Me5d0tXG2fQO//rAevk1i5IFjKZuOjTwyKB5SJIY4b8QTeg0g/50IU7Ht00Pxw6CK02dHS+FvXHasZlD3ckomqCDjStTBWdhJo5dST0CbOqalkkpLlCCbGA1yEQRsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAUIMeJ+EAo8eNpCG/nXImacjrKakbFnZYBGD/gqeTGaZynkX+jgBSructTHR83zSH+yELEhsAy+3BfK4EEihp+PEcRnK2fASVkHste8AQ7rlzC+HGGirlwrVhWCdizNUCGK80DE537IZ7nmZw6LFG9P5/Q2MvCsOCYjRUvMkukq6TdXBXR9tETwZ+0gpSfsOxjj0ZF7ftTRUSzx4rFfcbM9fRNdVizdOuKGc8HJPA5lLOxV6CyaYIvi3y5RlQI1OHeS34lE4w9CNPRFa/vdxXvN7ClyzA0HMFNWxBN7pC/Ht/FbhSvaAagJBHg+vCrcY5C26Oli7lAglf/zZrwUPs0w==";
ClientAttributeCertificateResource certRsc = accountClient.getCertficateResource("jwt.credential"); ClientAttributeCertificateResource certRsc = accountClient.getCertficateResource("jwt.credential");
// Upload privateKey and certificate as JKS store KeystoreUtil.KeystoreFormat preferredKeystoreType = KeystoreUtils.getPreferredKeystoreType();
MultipartFormDataOutput keyCertForm = new MultipartFormDataOutput();
keyCertForm.addFormData("keystoreFormat", "JKS", MediaType.TEXT_PLAIN_TYPE);
keyCertForm.addFormData("keyAlias", "clientkey", MediaType.TEXT_PLAIN_TYPE);
keyCertForm.addFormData("keyPassword", "keypass", MediaType.TEXT_PLAIN_TYPE);
keyCertForm.addFormData("storePassword", "storepass", MediaType.TEXT_PLAIN_TYPE);
URL idpMeta = getClass().getClassLoader().getResource("client-auth-test/keystore-client1.jks"); // Generate keystore file and upload privateKey and certificate from it as JKS store (or eventually PKCS12 or BCFKS store according to which one is preferred type)
byte [] content = Files.readAllBytes(Paths.get(idpMeta.toURI())); KeystoreUtils.KeystoreInfo generatedKeystore = KeystoreUtils.generateKeystore(folder, preferredKeystoreType, "clientkey", "storepass", "keypass");
keyCertForm.addFormData("file", content, MediaType.APPLICATION_OCTET_STREAM_TYPE); MultipartFormDataOutput keyCertForm = new MultipartFormDataOutput();
CertificateRepresentation cert = certRsc.uploadJks(keyCertForm);
// Returned cert is not the new state but rather what was extracted from inputs keyCertForm.addFormData("keystoreFormat", preferredKeystoreType.toString(), MediaType.TEXT_PLAIN_TYPE);
assertNotNull("cert not null", cert); keyCertForm.addFormData("keyAlias", "clientkey", MediaType.TEXT_PLAIN_TYPE);
assertEquals("cert properly extracted", certificate, cert.getCertificate()); keyCertForm.addFormData("keyPassword", "keypass", MediaType.TEXT_PLAIN_TYPE);
assertEquals("privateKey properly extracted", privateKey, cert.getPrivateKey()); keyCertForm.addFormData("storePassword", "storepass", MediaType.TEXT_PLAIN_TYPE);
// Get the certificate - to make sure cert was properly updated FileInputStream fs = new FileInputStream(generatedKeystore.getKeystoreFile());
cert = certRsc.getKeyInfo(); byte [] content = fs.readAllBytes();
assertEquals("cert properly set", certificate, cert.getCertificate()); fs.close();
assertEquals("privateKey properly set", privateKey, cert.getPrivateKey()); keyCertForm.addFormData("file", content, MediaType.APPLICATION_OCTET_STREAM_TYPE);
CertificateRepresentation cert = certRsc.uploadJks(keyCertForm);
// Upload a different certificate via /upload-certificate, privateKey should be nullified // Returned cert is not the new state but rather what was extracted from inputs
MultipartFormDataOutput form = new MultipartFormDataOutput(); assertNotNull("cert not null", cert);
form.addFormData("keystoreFormat", "Certificate PEM", MediaType.TEXT_PLAIN_TYPE); assertEquals("cert properly extracted", generatedKeystore.getCertificateInfo().getCertificate(), cert.getCertificate());
form.addFormData("file", certificate2.getBytes(Charset.forName("ASCII")), MediaType.APPLICATION_OCTET_STREAM_TYPE); assertEquals("privateKey properly extracted", generatedKeystore.getCertificateInfo().getPrivateKey(), cert.getPrivateKey());
cert = certRsc.uploadJksCertificate(form);
assertNotNull("cert not null", cert);
assertEquals("cert properly extracted", certificate2, cert.getCertificate());
assertNull("privateKey not included", cert.getPrivateKey());
// Get the certificate - to make sure cert was properly updated, and privateKey is null // Get the certificate - to make sure cert was properly updated
cert = certRsc.getKeyInfo(); cert = certRsc.getKeyInfo();
assertEquals("cert properly set", certificate2, cert.getCertificate()); assertEquals("cert properly set", generatedKeystore.getCertificateInfo().getCertificate(), cert.getCertificate());
assertNull("privateKey nullified", cert.getPrivateKey()); assertEquals("privateKey properly set", generatedKeystore.getCertificateInfo().getPrivateKey(), cert.getPrivateKey());
// Re-upload the private key // Upload a different certificate via /upload-certificate, privateKey should be nullified
certRsc.uploadJks(keyCertForm); MultipartFormDataOutput form = new MultipartFormDataOutput();
form.addFormData("keystoreFormat", "Certificate PEM", MediaType.TEXT_PLAIN_TYPE);
form.addFormData("file", certificate2.getBytes(Charset.forName("ASCII")), MediaType.APPLICATION_OCTET_STREAM_TYPE);
cert = certRsc.uploadJksCertificate(form);
assertNotNull("cert not null", cert);
assertEquals("cert properly extracted", certificate2, cert.getCertificate());
assertNull("privateKey not included", cert.getPrivateKey());
// Upload certificate as PEM via /upload - nullifies the private key // Get the certificate - to make sure cert was properly updated, and privateKey is null
form = new MultipartFormDataOutput(); cert = certRsc.getKeyInfo();
form.addFormData("keystoreFormat", "Certificate PEM", MediaType.TEXT_PLAIN_TYPE); assertEquals("cert properly set", certificate2, cert.getCertificate());
form.addFormData("file", certificate2.getBytes(Charset.forName("ASCII")), MediaType.APPLICATION_OCTET_STREAM_TYPE); assertNull("privateKey nullified", cert.getPrivateKey());
cert = certRsc.uploadJks(form);
assertNotNull("cert not null", cert);
assertEquals("cert properly extracted", certificate2, cert.getCertificate());
assertNull("privateKey not included", cert.getPrivateKey());
// Get the certificate again - to make sure cert is set, and privateKey is null // Re-upload the private key
cert = certRsc.getKeyInfo(); certRsc.uploadJks(keyCertForm);
assertEquals("cert properly set", certificate2, cert.getCertificate());
assertNull("privateKey nullified", cert.getPrivateKey());
// Upload certificate with header - should be stored without header // Upload certificate as PEM via /upload - nullifies the private key
form = new MultipartFormDataOutput(); form = new MultipartFormDataOutput();
form.addFormData("keystoreFormat", "Certificate PEM", MediaType.TEXT_PLAIN_TYPE); form.addFormData("keystoreFormat", "Certificate PEM", MediaType.TEXT_PLAIN_TYPE);
form.addFormData("file", certificate2.getBytes(Charset.forName("ASCII")), MediaType.APPLICATION_OCTET_STREAM_TYPE);
cert = certRsc.uploadJks(form);
assertNotNull("cert not null", cert);
assertEquals("cert properly extracted", certificate2, cert.getCertificate());
assertNull("privateKey not included", cert.getPrivateKey());
String certificate2WithHeaders = PemUtils.BEGIN_CERT + "\n" + certificate2 + "\n" + PemUtils.END_CERT; // Get the certificate again - to make sure cert is set, and privateKey is null
cert = certRsc.getKeyInfo();
assertEquals("cert properly set", certificate2, cert.getCertificate());
assertNull("privateKey nullified", cert.getPrivateKey());
form.addFormData("file", certificate2WithHeaders.getBytes(Charset.forName("ASCII")), MediaType.APPLICATION_OCTET_STREAM_TYPE); // Upload certificate with header - should be stored without header
cert = certRsc.uploadJks(form); form = new MultipartFormDataOutput();
assertNotNull("cert not null", cert); form.addFormData("keystoreFormat", "Certificate PEM", MediaType.TEXT_PLAIN_TYPE);
assertEquals("cert properly extracted", certificate2, cert.getCertificate());
assertNull("privateKey not included", cert.getPrivateKey());
// Get the certificate again - to make sure cert is set, and privateKey is null String certificate2WithHeaders = PemUtils.BEGIN_CERT + "\n" + certificate2 + "\n" + PemUtils.END_CERT;
cert = certRsc.getKeyInfo();
assertEquals("cert properly set", certificate2, cert.getCertificate()); form.addFormData("file", certificate2WithHeaders.getBytes(Charset.forName("ASCII")), MediaType.APPLICATION_OCTET_STREAM_TYPE);
assertNull("privateKey nullified", cert.getPrivateKey()); cert = certRsc.uploadJks(form);
assertNotNull("cert not null", cert);
assertEquals("cert properly extracted", certificate2, cert.getCertificate());
assertNull("privateKey not included", cert.getPrivateKey());
// Get the certificate again - to make sure cert is set, and privateKey is null
cert = certRsc.getKeyInfo();
assertEquals("cert properly set", certificate2, cert.getCertificate());
assertNull("privateKey nullified", cert.getPrivateKey());
} finally {
folder.delete();
}
} }
@Test @Test
@ -192,15 +203,17 @@ public class CredentialsTest extends AbstractClientTest {
// generate a key pair first // generate a key pair first
CertificateRepresentation certrep = certRsc.generate(); CertificateRepresentation certrep = certRsc.generate();
KeystoreUtil.KeystoreFormat preferredKeystoreType = KeystoreUtils.getPreferredKeystoreType();
// download the key and certificate // download the key and certificate
KeyStoreConfig config = new KeyStoreConfig(); KeyStoreConfig config = new KeyStoreConfig();
config.setFormat("JKS"); config.setFormat(preferredKeystoreType.toString());
config.setKeyAlias("alias"); config.setKeyAlias("alias");
config.setKeyPassword("keyPass"); config.setKeyPassword("keyPass");
config.setStorePassword("storePass"); config.setStorePassword("storePass");
byte[] result = certRsc.getKeystore(config); byte[] result = certRsc.getKeystore(config);
KeyStore keyStore = KeyStore.getInstance("JKS"); KeyStore keyStore = CryptoIntegration.getProvider().getKeyStore(preferredKeystoreType);
keyStore.load(new ByteArrayInputStream(result), "storePass".toCharArray()); keyStore.load(new ByteArrayInputStream(result), "storePass".toCharArray());
Key key = keyStore.getKey("alias", "keyPass".toCharArray()); Key key = keyStore.getKey("alias", "keyPass".toCharArray());
Certificate cert = keyStore.getCertificate("alias"); Certificate cert = keyStore.getCertificate("alias");
@ -220,13 +233,15 @@ public class CredentialsTest extends AbstractClientTest {
// generate a key pair first // generate a key pair first
CertificateRepresentation firstcert = certRsc.generate(); CertificateRepresentation firstcert = certRsc.generate();
KeystoreUtil.KeystoreFormat preferredKeystoreType = KeystoreUtils.getPreferredKeystoreType();
KeyStoreConfig config = new KeyStoreConfig(); KeyStoreConfig config = new KeyStoreConfig();
config.setFormat("JKS"); config.setFormat(preferredKeystoreType.toString());
config.setKeyAlias("alias"); config.setKeyAlias("alias");
config.setKeyPassword("keyPass"); config.setKeyPassword("keyPass");
config.setStorePassword("storePass"); config.setStorePassword("storePass");
byte[] result = certRsc.generateAndGetKeystore(config); byte[] result = certRsc.generateAndGetKeystore(config);
KeyStore keyStore = KeyStore.getInstance("JKS"); KeyStore keyStore = CryptoIntegration.getProvider().getKeyStore(preferredKeystoreType);
keyStore.load(new ByteArrayInputStream(result), "storePass".toCharArray()); keyStore.load(new ByteArrayInputStream(result), "storePass".toCharArray());
Key key = keyStore.getKey("alias", "keyPass".toCharArray()); Key key = keyStore.getKey("alias", "keyPass".toCharArray());
Certificate cert = keyStore.getCertificate("alias"); Certificate cert = keyStore.getCertificate("alias");

View file

@ -17,11 +17,11 @@
package org.keycloak.testsuite.keys; package org.keycloak.testsuite.keys;
import org.apache.commons.io.IOUtils;
import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.graphene.page.Page;
import org.junit.Rule; 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.KeystoreUtil;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.jose.jws.AlgorithmType; import org.keycloak.jose.jws.AlgorithmType;
import org.keycloak.keys.JavaKeystoreKeyProviderFactory; import org.keycloak.keys.JavaKeystoreKeyProviderFactory;
@ -31,18 +31,20 @@ import org.keycloak.representations.idm.ErrorRepresentation;
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.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.util.KeystoreUtils;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.List; import java.util.List;
import static org.junit.Assert.*; import static io.smallrye.common.constraint.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.keycloak.common.util.KeystoreUtil.KeystoreFormat.PKCS12;
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
/** /**
@ -50,10 +52,6 @@ import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
*/ */
public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest { public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
private static final String PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsPAJ/X39oNRkoS+baWVhAghfO86ZPfkSHm4evmMDhbA0KqW1/hg55qUJoT91ytGozIsIxoCLKzQvZTluRpt0AMp7cmfaGWBQ8cBtb8/BL+5FkUucigmOcTrfPq9/xR9g4AMSXRItjLRsJPy2Bnjau64DVQ3N5NVbWAMw7/1XjuobEyPnw0RLqEr/TxWMteuaiV1n8amIAiT91xZ8UFyPv3urCkAz+r+iyVvdJcZwn2tUL6KLM7qX/HSX8SUtPrIMB8EdW1yNt5McO8Ro5GxwiyXimDKbY9ur2WP8/wrdk/0TkoUYeI1UsnFyoJcqqg2+1T+dNAMtJhF7uDhURVQ33QIDAQAB";
private static final String CERTIFICATE = "MIIDeTCCAmGgAwIBAgIEbhSauDANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYDVQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3duMCAXDTE2MTAxMzE4MjUxNFoYDzIyOTAwNzI4MTgyNTE0WjBsMRAwDgYDVQQGEwdVbmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYDVQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3duMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsPAJ/X39oNRkoS+baWVhAghfO86ZPfkSHm4evmMDhbA0KqW1/hg55qUJoT91ytGozIsIxoCLKzQvZTluRpt0AMp7cmfaGWBQ8cBtb8/BL+5FkUucigmOcTrfPq9/xR9g4AMSXRItjLRsJPy2Bnjau64DVQ3N5NVbWAMw7/1XjuobEyPnw0RLqEr/TxWMteuaiV1n8amIAiT91xZ8UFyPv3urCkAz+r+iyVvdJcZwn2tUL6KLM7qX/HSX8SUtPrIMB8EdW1yNt5McO8Ro5GxwiyXimDKbY9ur2WP8/wrdk/0TkoUYeI1UsnFyoJcqqg2+1T+dNAMtJhF7uDhURVQ33QIDAQABoyEwHzAdBgNVHQ4EFgQUgz0ABmkImZUEO2/w0shoH4rp6pwwDQYJKoZIhvcNAQELBQADggEBAK+syjqfFXmv7942+ZfmJfb4i/JilhwSyA2G1VvGR39dLW1nPmKMMUY6kKgJ2NZgaCGvJ4jxDhfNJ1jPG7rcO/eQuF3cx9r+nHiTcJ5PNLqG2q4dNNFshJ8aGuIaTQEB7S1OlGsEj0rd0YlJ+LTrFfEHsnsJvpvDRLdVMklib5fPk4W8ziuQ3rr6T/a+be3zfAqmFZx8j6E46jz9QO841uwqdzcR9kfSHS/76TNGZv8OB6jheyHrUdBygR85iizHgMqats/0zWmKEAvSp/DhAfyIFp8zZHvPjmpBl+mfmAqnrYY0oJRb5rRXmL8DKq5plc7jgO1H6aHh5mV6slXQDEw=";
@Rule @Rule
public TemporaryFolder folder = new TemporaryFolder(); public TemporaryFolder folder = new TemporaryFolder();
@ -65,7 +63,7 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
@Page @Page
protected LoginPage loginPage; protected LoginPage loginPage;
private File file; private KeystoreUtils.KeystoreInfo generatedKeystore;
@Override @Override
public void addTestRealms(List<RealmRepresentation> testRealms) { public void addTestRealms(List<RealmRepresentation> testRealms) {
@ -73,24 +71,32 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
testRealms.add(realm); testRealms.add(realm);
} }
@Override @Test
public void beforeAbstractKeycloakTest() throws Exception { public void createJks() throws Exception {
super.beforeAbstractKeycloakTest(); createSuccess(KeystoreUtil.KeystoreFormat.JKS);
file = folder.newFile("keystore.jsk");
InputStream resourceAsStream = JavaKeystoreKeyProviderTest.class.getResourceAsStream("keystore.jks");
IOUtils.copy(resourceAsStream, new FileOutputStream(file));
} }
@Test @Test
public void create() throws Exception { public void createPkcs12() throws Exception {
createSuccess(PKCS12);
}
@Test
public void createBcfks() throws Exception {
createSuccess(KeystoreUtil.KeystoreFormat.BCFKS);
}
private void createSuccess(KeystoreUtil.KeystoreFormat keystoreType) throws Exception {
KeystoreUtils.assumeKeystoreTypeSupported(keystoreType);
generateKeystore(keystoreType);
long priority = System.currentTimeMillis(); long priority = System.currentTimeMillis();
ComponentRepresentation rep = createRep("valid", priority); ComponentRepresentation rep = createRep("valid", priority);
Response response = adminClient.realm("test").components().add(rep); Response response = adminClient.realm("test").components().add(rep);
String id = ApiUtil.getCreatedId(response); String id = ApiUtil.getCreatedId(response);
getCleanup().addComponentId(id);
ComponentRepresentation createdRep = adminClient.realm("test").components().component(id).toRepresentation(); ComponentRepresentation createdRep = adminClient.realm("test").components().component(id).toRepresentation();
assertEquals(5, createdRep.getConfig().size()); assertEquals(5, createdRep.getConfig().size());
@ -105,12 +111,13 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
assertEquals(id, key.getProviderId()); assertEquals(id, key.getProviderId());
assertEquals(AlgorithmType.RSA.name(), key.getType()); assertEquals(AlgorithmType.RSA.name(), key.getType());
assertEquals(priority, key.getProviderPriority()); assertEquals(priority, key.getProviderPriority());
assertEquals(PUBLIC_KEY, key.getPublicKey()); assertEquals(generatedKeystore.getCertificateInfo().getPublicKey(), key.getPublicKey());
assertEquals(CERTIFICATE, key.getCertificate()); assertEquals(generatedKeystore.getCertificateInfo().getCertificate(), key.getCertificate());
} }
@Test @Test
public void invalidKeystore() throws Exception { public void invalidKeystore() throws Exception {
generateKeystore(KeystoreUtils.getPreferredKeystoreType());
ComponentRepresentation rep = createRep("valid", System.currentTimeMillis()); ComponentRepresentation rep = createRep("valid", System.currentTimeMillis());
rep.getConfig().putSingle("keystore", "/nosuchfile"); rep.getConfig().putSingle("keystore", "/nosuchfile");
@ -120,6 +127,7 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
@Test @Test
public void invalidKeystorePassword() throws Exception { public void invalidKeystorePassword() throws Exception {
generateKeystore(KeystoreUtils.getPreferredKeystoreType());
ComponentRepresentation rep = createRep("valid", System.currentTimeMillis()); ComponentRepresentation rep = createRep("valid", System.currentTimeMillis());
rep.getConfig().putSingle("keystore", "invalid"); rep.getConfig().putSingle("keystore", "invalid");
@ -129,6 +137,7 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
@Test @Test
public void invalidKeyAlias() throws Exception { public void invalidKeyAlias() throws Exception {
generateKeystore(KeystoreUtils.getPreferredKeystoreType());
ComponentRepresentation rep = createRep("valid", System.currentTimeMillis()); ComponentRepresentation rep = createRep("valid", System.currentTimeMillis());
rep.getConfig().putSingle("keyAlias", "invalid"); rep.getConfig().putSingle("keyAlias", "invalid");
@ -138,10 +147,22 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
@Test @Test
public void invalidKeyPassword() throws Exception { public void invalidKeyPassword() throws Exception {
KeystoreUtil.KeystoreFormat keystoreType = KeystoreUtils.getPreferredKeystoreType();
if (keystoreType == PKCS12) {
// only the keyStore password is significant with PKCS12. Hence we need to test with different keystore type
String[] supportedKsTypes = KeystoreUtils.getSupportedKeystoreTypes();
if (supportedKsTypes.length <= 1) {
Assert.fail("Only PKCS12 type is supported, but invalidKeyPassword() scenario cannot be tested with it");
}
keystoreType = Enum.valueOf(KeystoreUtil.KeystoreFormat.class, supportedKsTypes[1]);
log.infof("Fallback to keystore type '%s' for the invalidKeyPassword() test", keystoreType);
}
generateKeystore(keystoreType);
ComponentRepresentation rep = createRep("valid", System.currentTimeMillis()); ComponentRepresentation rep = createRep("valid", System.currentTimeMillis());
rep.getConfig().putSingle("keyPassword", "invalid"); rep.getConfig().putSingle("keyPassword", "invalid");
Response response = adminClient.realm("test").components().add(rep); Response response = adminClient.realm("test").components().add(rep);
Assert.assertEquals(400, response.getStatus());
assertErrror(response, "Failed to load keys. Keystore on server can not be recovered."); assertErrror(response, "Failed to load keys. Keystore on server can not be recovered.");
} }
@ -163,12 +184,16 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
rep.setProviderType(KeyProvider.class.getName()); rep.setProviderType(KeyProvider.class.getName());
rep.setConfig(new MultivaluedHashMap<>()); rep.setConfig(new MultivaluedHashMap<>());
rep.getConfig().putSingle("priority", Long.toString(priority)); rep.getConfig().putSingle("priority", Long.toString(priority));
rep.getConfig().putSingle("keystore", file.getAbsolutePath()); rep.getConfig().putSingle("keystore", generatedKeystore.getKeystoreFile().getAbsolutePath());
rep.getConfig().putSingle("keystorePassword", "password"); rep.getConfig().putSingle("keystorePassword", "password");
rep.getConfig().putSingle("keyAlias", "selfsigned"); rep.getConfig().putSingle("keyAlias", "selfsigned");
rep.getConfig().putSingle("keyPassword", "password"); rep.getConfig().putSingle("keyPassword", "password");
return rep; return rep;
} }
private void generateKeystore(KeystoreUtil.KeystoreFormat keystoreType) throws Exception {
this.generatedKeystore = KeystoreUtils.generateKeystore(folder, keystoreType, "selfsigned", "password", "password");
}
} }

View file

@ -32,8 +32,11 @@ import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicNameValuePair;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.adapters.AdapterUtils; import org.keycloak.adapters.AdapterUtils;
@ -85,13 +88,12 @@ import org.keycloak.testsuite.client.resources.TestOIDCEndpointsApplicationResou
import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource; import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;
import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.testsuite.util.ClientBuilder;
import org.keycloak.testsuite.util.ClientManager; import org.keycloak.testsuite.util.ClientManager;
import org.keycloak.testsuite.util.KeystoreUtils;
import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.UserBuilder; import org.keycloak.testsuite.util.UserBuilder;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import com.sun.jna.StringArray;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
@ -130,6 +132,21 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
@Rule @Rule
public AssertEvents events = new AssertEvents(this); public AssertEvents events = new AssertEvents(this);
@ClassRule
public static TemporaryFolder folder = new TemporaryFolder();
private static KeystoreUtils.KeystoreInfo generatedKeystoreClient1;
private static KeyPair keyPairClient1;
@BeforeClass
public static void generateClient1KeyPair() throws Exception {
generatedKeystoreClient1 = KeystoreUtils.generateKeystore(folder, KeystoreFormat.JKS, "clientkey", "storepass", "keypass");
PublicKey publicKey = PemUtils.decodePublicKey(generatedKeystoreClient1.getCertificateInfo().getPublicKey());
PrivateKey privateKey = PemUtils.decodePrivateKey(generatedKeystoreClient1.getCertificateInfo().getPrivateKey());
keyPairClient1 = new KeyPair(publicKey, privateKey);
}
private static String client1SAUserId; private static String client1SAUserId;
private static RealmRepresentation testRealm; private static RealmRepresentation testRealm;
@ -151,7 +168,7 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
app1 = ClientBuilder.create() app1 = ClientBuilder.create()
.id(KeycloakModelUtils.generateId()) .id(KeycloakModelUtils.generateId())
.clientId("client1") .clientId("client1")
.attribute(JWTClientAuthenticator.CERTIFICATE_ATTR, "MIICnTCCAYUCBgFPPLDaTzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE3MjI0N1oXDTI1MDgxNzE3MjQyN1owEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIUjjgv+V3s96O+Za9002Lp/trtGuHBeaeVL9dFKMKzO2MPqdRmHB4PqNlDdd28Rwf5Xn6iWdFpyUKOnI/yXDLhdcuFpR0sMNK/C9Lt+hSpPFLuzDqgtPgDotlMxiHIWDOZ7g9/gPYNXbNvjv8nSiyqoguoCQiiafW90bPHsiVLdP7ZIUwCcfi1qQm7FhxRJ1NiW5dvUkuCnnWEf0XR+Wzc5eC9EgB0taLFiPsSEIlWMm5xlahYyXkPdNOqZjiRnrTWm5Y4uk8ZcsD/KbPTf/7t7cQXipVaswgjdYi1kK2/zRwOhg1QwWFX/qmvdd+fLxV0R6VqRDhn7Qep2cxwMxLsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAKE6OA46sf20bz8LZPoiNsqRwBUDkaMGXfnob7s/hJZIIwDEx0IAQ3uKsG7q9wb+aA6s+v7S340zb2k3IxuhFaHaZpAd4CyR5cn1FHylbzoZ7rI/3ASqHDqpljdJaFqPH+m7nZWtyDvtZf+gkZ8OjsndwsSBK1d/jMZPp29qYbl1+XfO7RCp/jDqro/R3saYFaIFiEZPeKn1hUJn6BO48vxH1xspSu9FmlvDOEAOz4AuM58z4zRMP49GcFdCWr1wkonJUHaSptJaQwmBwLFUkCbE5I1ixGMb7mjEud6Y5jhfzJiZMo2U8RfcjNbrN0diZl3jB6LQIwESnhYSghaTjNQ==") .attribute(JWTClientAuthenticator.CERTIFICATE_ATTR, generatedKeystoreClient1.getCertificateInfo().getCertificate())
.attribute(OIDCConfigAttributes.USE_REFRESH_TOKEN_FOR_CLIENT_CREDENTIALS_GRANT, "true") .attribute(OIDCConfigAttributes.USE_REFRESH_TOKEN_FOR_CLIENT_CREDENTIALS_GRANT, "true")
.authenticatorType(JWTClientAuthenticator.PROVIDER_ID) .authenticatorType(JWTClientAuthenticator.PROVIDER_ID)
.serviceAccountsEnabled(true) .serviceAccountsEnabled(true)
@ -171,9 +188,6 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
realmBuilder.client(app2); realmBuilder.client(app2);
// This one is for keystore-client2.p12 , which doesn't work on Sun JDK
// app2.setAttribute(JWTClientAuthenticator.CERTIFICATE_ATTR, "MIICnTCCAYUCBgFPPLGHHjANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE3MjMzMVoXDTI1MDgxNzE3MjUxMVowEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIsatXj38fFD9fHslNrsWrubobudXYwwdZpGYqkHIhuDeSojGvhBSLmKIFmtbHMVcLEbS0dIEsSbNVrwjdFfuRuvd9Vu6Ng0JUC8fRhSeQniC3jcBuP8P4WlXK4+ir3Wlya+T6Hum9b68BiH0KyNZtFGJ6zLHuCcq9Bl0JifvibnUkDeTZPwgJNA9+GxS/x8fAkApcAbJrgBZvr57PwhbgHoZdB8aAY5f5ogbGzKDtSUMvFh+Jah39gWtn7p3VOuuMXA8SugogoH8C5m2itrPBL1UPhAcKUeWiqx4SmZe/lZo7x2WbSecNiFaiqBhIW+QbqCYW6I4u0YvuLuEe3+TC8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAZzW5DZviCxUQdV5Ab07PZkUfvImHZ73oWWHZqzUQtZtbVdzfp3cnbb2wyXtlOvingO3hgpoTxV8vbKgLbIQfvkGGHBG1F5e0QVdtikfdcwWb7cy4/9F80OD7cgG0ZAzFbQ8ZY7iS3PToBp3+4tbIK2NK0ntt/MYgJnPbHeG4V4qfgUbFm1YgEK7WpbSVU8jGuJ5DWE+mlYgECZKZ5TSlaVGs2XOm6WXrJScucNekwcBWWiHyRsFHZEDzWmzt8TLTLnnb0vVjhx3qCYxah3RbyyMZm6WLZlLAaGEcwNDO8jaA3hAjrxoOA1xEaolQfGVsb/ElelHcR1Zfe0u4Ekd4tw==");
defaultUser = UserBuilder.create() defaultUser = UserBuilder.create()
.id(KeycloakModelUtils.generateId()) .id(KeycloakModelUtils.generateId())
//.serviceAccountId(app1.getClientId()) //.serviceAccountId(app1.getClientId())
@ -590,14 +604,22 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
@Test @Test
public void testClientWithGeneratedKeysJKS() throws Exception { public void testClientWithGeneratedKeysJKS() throws Exception {
KeystoreUtils.assumeKeystoreTypeSupported(KeystoreFormat.JKS);
testClientWithGeneratedKeys("JKS"); testClientWithGeneratedKeys("JKS");
} }
@Test @Test
public void testClientWithGeneratedKeysPKCS12() throws Exception { public void testClientWithGeneratedKeysPKCS12() throws Exception {
KeystoreUtils.assumeKeystoreTypeSupported(KeystoreFormat.PKCS12);
testClientWithGeneratedKeys("PKCS12"); testClientWithGeneratedKeys("PKCS12");
} }
@Test
public void testClientWithGeneratedKeysBCFKS() throws Exception {
KeystoreUtils.assumeKeystoreTypeSupported(KeystoreFormat.BCFKS);
testClientWithGeneratedKeys(KeystoreFormat.BCFKS.toString());
}
private void testClientWithGeneratedKeys(String format) throws Exception { private void testClientWithGeneratedKeys(String format) throws Exception {
ClientRepresentation client = app3; ClientRepresentation client = app3;
UserRepresentation user = defaultUser; UserRepresentation user = defaultUser;
@ -664,12 +686,22 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
@Test @Test
public void testUploadKeystoreJKS() throws Exception { public void testUploadKeystoreJKS() throws Exception {
testUploadKeystore("JKS", "client-auth-test/keystore-client1.jks", "clientkey", "storepass"); KeystoreUtils.assumeKeystoreTypeSupported(KeystoreFormat.JKS);
testUploadKeystore("JKS", generatedKeystoreClient1.getKeystoreFile().getAbsolutePath(), "clientkey", "storepass");
} }
@Test @Test
public void testUploadKeystorePKCS12() throws Exception { public void testUploadKeystorePKCS12() throws Exception {
testUploadKeystore("PKCS12", "client-auth-test/keystore-client2.p12", "clientkey", "pwd2"); KeystoreUtils.assumeKeystoreTypeSupported(KeystoreFormat.PKCS12);
KeystoreUtils.KeystoreInfo ksInfo = KeystoreUtils.generateKeystore(folder, KeystoreFormat.PKCS12, "clientkey", "pwd2", "keypass");
testUploadKeystore(KeystoreFormat.PKCS12.toString(), ksInfo.getKeystoreFile().getAbsolutePath(), "clientkey", "pwd2");
}
@Test
public void testUploadKeystoreBCFKS() throws Exception {
KeystoreUtils.assumeKeystoreTypeSupported(KeystoreFormat.BCFKS);
KeystoreUtils.KeystoreInfo ksInfo = KeystoreUtils.generateKeystore(folder, KeystoreFormat.BCFKS, "clientkey", "pwd2", "keypass");
testUploadKeystore(KeystoreFormat.BCFKS.toString(), ksInfo.getKeystoreFile().getAbsolutePath(), "clientkey", "pwd2");
} }
@Test @Test
@ -696,10 +728,10 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
// Load the keystore file // Load the keystore file
URL fileUrl = (getClass().getClassLoader().getResource(filePath)); URL fileUrl = (getClass().getClassLoader().getResource(filePath));
if (fileUrl == null) { File keystoreFile = fileUrl != null ? new File(fileUrl.getFile()) : new File(filePath);
throw new IOException("File not found: " + filePath); if (!keystoreFile.exists()) {
throw new IOException("File not found: " + keystoreFile.getAbsolutePath());
} }
File keystoreFile = new File(fileUrl.getFile());
// Get admin access token, no matter it's master realm's admin // Get admin access token, no matter it's master realm's admin
OAuthClient.AccessTokenResponse accessTokenResponse = oauth.doGrantAccessTokenRequest( OAuthClient.AccessTokenResponse accessTokenResponse = oauth.doGrantAccessTokenRequest(
@ -792,7 +824,7 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
@Test @Test
public void testAssertionMissingIssuer() throws Exception { public void testAssertionMissingIssuer() throws Exception {
String invalidJwt = getClientSignedJWT(getClient1KeyPair(), null); String invalidJwt = getClientSignedJWT(keyPairClient1, null);
List<NameValuePair> parameters = new LinkedList<NameValuePair>(); List<NameValuePair> parameters = new LinkedList<NameValuePair>();
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS)); parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS));
@ -807,7 +839,7 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
@Test @Test
public void testAssertionUnknownClient() throws Exception { public void testAssertionUnknownClient() throws Exception {
String invalidJwt = getClientSignedJWT(getClient1KeyPair(), "unknown-client"); String invalidJwt = getClientSignedJWT(keyPairClient1, "unknown-client");
List<NameValuePair> parameters = new LinkedList<NameValuePair>(); List<NameValuePair> parameters = new LinkedList<NameValuePair>();
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS)); parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS));
@ -1072,7 +1104,7 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
private OAuthClient.AccessTokenResponse testMissingClaim(int tokenTimeOffset, String... claims) throws Exception { private OAuthClient.AccessTokenResponse testMissingClaim(int tokenTimeOffset, String... claims) throws Exception {
CustomJWTClientCredentialsProvider jwtProvider = new CustomJWTClientCredentialsProvider(); CustomJWTClientCredentialsProvider jwtProvider = new CustomJWTClientCredentialsProvider();
jwtProvider.setupKeyPair(getClient1KeyPair()); jwtProvider.setupKeyPair(keyPairClient1);
jwtProvider.setTokenTimeout(10); jwtProvider.setTokenTimeout(10);
for (String claim : claims) { for (String claim : claims) {
@ -1304,19 +1336,14 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
return getClientSignedJWT(getClient2KeyPair(), "client2", algorithm); return getClientSignedJWT(getClient2KeyPair(), "client2", algorithm);
} }
private String getClient1SignedJWT() { private String getClient1SignedJWT() throws Exception {
return getClientSignedJWT(getClient1KeyPair(), "client1", Algorithm.RS256); return getClientSignedJWT(keyPairClient1, "client1", Algorithm.RS256);
} }
private String getClient2SignedJWT() { private String getClient2SignedJWT() {
return getClientSignedJWT(getClient2KeyPair(), "client2", Algorithm.RS256); return getClientSignedJWT(getClient2KeyPair(), "client2", Algorithm.RS256);
} }
private KeyPair getClient1KeyPair() {
return KeystoreUtil.loadKeyPairFromKeystore("classpath:client-auth-test/keystore-client1.jks",
"storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS);
}
private KeyPair getClient2KeyPair() { private KeyPair getClient2KeyPair() {
return KeystoreUtil.loadKeyPairFromKeystore("classpath:client-auth-test/keystore-client2.jks", return KeystoreUtil.loadKeyPairFromKeystore("classpath:client-auth-test/keystore-client2.jks",
"storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS); "storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS);

View file

@ -263,6 +263,7 @@
<keycloak.x509cert.lookup.provider>default</keycloak.x509cert.lookup.provider> <keycloak.x509cert.lookup.provider>default</keycloak.x509cert.lookup.provider>
<auth.server.quarkus.cluster.config>local</auth.server.quarkus.cluster.config> <auth.server.quarkus.cluster.config>local</auth.server.quarkus.cluster.config>
<auth.server.fips.mode>disabled</auth.server.fips.mode> <auth.server.fips.mode>disabled</auth.server.fips.mode>
<auth.server.supported.keystore.types>JKS,PKCS12,BCFKS</auth.server.supported.keystore.types>
</properties> </properties>
<build> <build>
@ -685,6 +686,7 @@
<!-- FIPS 140-2 --> <!-- FIPS 140-2 -->
<auth.server.fips.mode>${auth.server.fips.mode}</auth.server.fips.mode> <auth.server.fips.mode>${auth.server.fips.mode}</auth.server.fips.mode>
<auth.server.fips.keystore.type>${auth.server.fips.keystore.type}</auth.server.fips.keystore.type> <auth.server.fips.keystore.type>${auth.server.fips.keystore.type}</auth.server.fips.keystore.type>
<auth.server.supported.keystore.types>${auth.server.supported.keystore.types}</auth.server.supported.keystore.types>
<!-- <!--
~ Used for Wildfly Elytron 1.13.0.CR3+ RESTEasy client SSL truststore configuration. ~ Used for Wildfly Elytron 1.13.0.CR3+ RESTEasy client SSL truststore configuration.
@ -1587,6 +1589,8 @@
<properties> <properties>
<auth.server.fips.mode>enabled</auth.server.fips.mode> <auth.server.fips.mode>enabled</auth.server.fips.mode>
<auth.server.supported.keystore.types>PKCS12,BCFKS</auth.server.supported.keystore.types>
<auth.server.keystore.type>pkcs12</auth.server.keystore.type> <auth.server.keystore.type>pkcs12</auth.server.keystore.type>
<auth.server.keystore>${auth.server.config.dir}/keycloak-fips.keystore.${auth.server.keystore.type}</auth.server.keystore> <auth.server.keystore>${auth.server.config.dir}/keycloak-fips.keystore.${auth.server.keystore.type}</auth.server.keystore>
<auth.server.keystore.password>passwordpassword</auth.server.keystore.password> <auth.server.keystore.password>passwordpassword</auth.server.keystore.password>