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:
parent
5ebb6e9c10
commit
55c514ad56
21 changed files with 635 additions and 152 deletions
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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<>();
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Binary file not shown.
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue