diff --git a/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProvider.java b/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProvider.java index 07e91a4390..d7fa87544d 100644 --- a/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProvider.java +++ b/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProvider.java @@ -53,10 +53,8 @@ public class JavaKeystoreKeyProvider extends AbstractRsaKeyProvider { KeyPair keyPair = new KeyPair(publicKey, privateKey); - X509Certificate certificate; - if (model.contains(JavaKeystoreKeyProviderFactory.CERTIFICATE_ALIAS_KEY)) { - certificate = (X509Certificate) keyStore.getCertificate(model.get(JavaKeystoreKeyProviderFactory.CERTIFICATE_ALIAS_KEY)); - } else { + X509Certificate certificate = (X509Certificate) keyStore.getCertificate(model.get(JavaKeystoreKeyProviderFactory.KEY_ALIAS_KEY)); + if (certificate == null) { certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName()); } diff --git a/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProviderFactory.java b/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProviderFactory.java index 6b6e530273..069160758b 100644 --- a/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProviderFactory.java +++ b/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProviderFactory.java @@ -48,9 +48,6 @@ public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactor public static String KEY_PASSWORD_KEY = "keyPassword"; public static ProviderConfigProperty KEY_PASSWORD_PROPERTY = new ProviderConfigProperty(KEY_PASSWORD_KEY, "Private Key password", "Password for the private key", STRING_TYPE, null, true); - public static String CERTIFICATE_ALIAS_KEY = "certificateAlias"; - public static ProviderConfigProperty CERTIFICATE_ALIAS_PROPERTY = new ProviderConfigProperty(CERTIFICATE_ALIAS_KEY, "Certificate Alias", "Alias for the certificate", STRING_TYPE, null); - private static final String HELP_TEXT = "Loads keys from a Java keys file"; private static final List CONFIG_PROPERTIES = AbstractRsaKeyProviderFactory.configurationBuilder() @@ -58,7 +55,6 @@ public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactor .property(KEYSTORE_PASSWORD_PROPERTY) .property(KEY_ALIAS_PROPERTY) .property(KEY_PASSWORD_PROPERTY) - .property(CERTIFICATE_ALIAS_PROPERTY) .build(); @Override @@ -74,8 +70,7 @@ public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactor .checkSingle(KEYSTORE_PROPERTY, true) .checkSingle(KEYSTORE_PASSWORD_PROPERTY, true) .checkSingle(KEY_ALIAS_PROPERTY, true) - .checkSingle(KEY_PASSWORD_PROPERTY, true) - .checkSingle(CERTIFICATE_ALIAS_PROPERTY, false); + .checkSingle(KEY_PASSWORD_PROPERTY, true); try { new JavaKeystoreKeyProvider(session.getContext().getRealm(), model) diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/JavaKeystoreKeyProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/JavaKeystoreKeyProviderTest.java new file mode 100644 index 0000000000..cac8a27b31 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/JavaKeystoreKeyProviderTest.java @@ -0,0 +1,181 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testsuite.keys; + +import org.apache.commons.io.IOUtils; +import org.jboss.arquillian.graphene.page.Page; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.keycloak.common.util.KeyUtils; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.common.util.PemUtils; +import org.keycloak.keys.Attributes; +import org.keycloak.keys.GeneratedRsaKeyProviderFactory; +import org.keycloak.keys.JavaKeystoreKeyProviderFactory; +import org.keycloak.keys.KeyMetadata; +import org.keycloak.keys.KeyProvider; +import org.keycloak.keys.RsaKeyProviderFactory; +import org.keycloak.representations.idm.ComponentRepresentation; +import org.keycloak.representations.idm.ErrorRepresentation; +import org.keycloak.representations.idm.KeysMetadataRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.admin.ApiUtil; +import org.keycloak.testsuite.pages.AppPage; +import org.keycloak.testsuite.pages.LoginPage; + +import javax.ws.rs.core.Response; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.security.KeyPair; +import java.security.cert.Certificate; +import java.security.interfaces.RSAPublicKey; +import java.util.List; + +import static org.junit.Assert.*; +import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; + +/** + * @author Stian Thorgersen + */ +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 + public TemporaryFolder folder = new TemporaryFolder(); + + @Rule + public AssertEvents events = new AssertEvents(this); + + @Page + protected AppPage appPage; + + @Page + protected LoginPage loginPage; + private File file; + + @Override + public void addTestRealms(List testRealms) { + RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class); + testRealms.add(realm); + } + + @Override + public void beforeAbstractKeycloakTest() throws Exception { + super.beforeAbstractKeycloakTest(); + + file = folder.newFile("keystore.jsk"); + + InputStream resourceAsStream = JavaKeystoreKeyProviderTest.class.getResourceAsStream("keystore.jks"); + IOUtils.copy(resourceAsStream, new FileOutputStream(file)); + } + + @Test + public void create() throws Exception { + long priority = System.currentTimeMillis(); + + ComponentRepresentation rep = createRep("valid", priority); + + Response response = adminClient.realm("test").components().add(rep); + String id = ApiUtil.getCreatedId(response); + + ComponentRepresentation createdRep = adminClient.realm("test").components().component(id).toRepresentation(); + assertEquals(5, createdRep.getConfig().size()); + assertEquals(Long.toString(priority), createdRep.getConfig().getFirst("priority")); + assertEquals(ComponentRepresentation.SECRET_VALUE, createdRep.getConfig().getFirst("keystorePassword")); + assertEquals(ComponentRepresentation.SECRET_VALUE, createdRep.getConfig().getFirst("keyPassword")); + + KeysMetadataRepresentation keys = adminClient.realm("test").keys().getKeyMetadata(); + + KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0); + + assertEquals(id, key.getProviderId()); + assertEquals(KeyMetadata.Type.RSA.name(), key.getType()); + assertEquals(priority, key.getProviderPriority()); + assertEquals(PUBLIC_KEY, key.getPublicKey()); + assertEquals(CERTIFICATE, key.getCertificate()); + } + + @Test + public void invalidKeystore() throws Exception { + ComponentRepresentation rep = createRep("valid", System.currentTimeMillis()); + rep.getConfig().putSingle("keystore", "/nosuchfile"); + + Response response = adminClient.realm("test").components().add(rep); + assertErrror(response, "Failed to load keys"); + } + + @Test + public void invalidKeystorePassword() throws Exception { + ComponentRepresentation rep = createRep("valid", System.currentTimeMillis()); + rep.getConfig().putSingle("keystore", "invalid"); + + Response response = adminClient.realm("test").components().add(rep); + assertErrror(response, "Failed to load keys"); + } + + @Test + public void invalidKeyAlias() throws Exception { + ComponentRepresentation rep = createRep("valid", System.currentTimeMillis()); + rep.getConfig().putSingle("keyAlias", "invalid"); + + Response response = adminClient.realm("test").components().add(rep); + assertErrror(response, "Failed to load keys"); + } + + @Test + public void invalidKeyPassword() throws Exception { + ComponentRepresentation rep = createRep("valid", System.currentTimeMillis()); + rep.getConfig().putSingle("keyPassword", "invalid"); + + Response response = adminClient.realm("test").components().add(rep); + assertErrror(response, "Failed to load keys"); + } + + protected void assertErrror(Response response, String error) { + if (!response.hasEntity()) { + fail("No error message set"); + } + + ErrorRepresentation errorRepresentation = response.readEntity(ErrorRepresentation.class); + assertEquals(error, errorRepresentation.getErrorMessage()); + } + + protected ComponentRepresentation createRep(String name, long priority) { + ComponentRepresentation rep = new ComponentRepresentation(); + rep.setName(name); + rep.setParentId("test"); + rep.setProviderId(JavaKeystoreKeyProviderFactory.ID); + rep.setProviderType(KeyProvider.class.getName()); + rep.setConfig(new MultivaluedHashMap<>()); + rep.getConfig().putSingle("priority", Long.toString(priority)); + rep.getConfig().putSingle("keystore", file.getAbsolutePath()); + rep.getConfig().putSingle("keystorePassword", "password"); + rep.getConfig().putSingle("keyAlias", "selfsigned"); + rep.getConfig().putSingle("keyPassword", "password"); + return rep; + } + +} + diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/KeyRotationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/KeyRotationTest.java index fd4fdfede0..964832cac0 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/KeyRotationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/KeyRotationTest.java @@ -17,6 +17,8 @@ package org.keycloak.testsuite.keys; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import org.jboss.arquillian.graphene.page.Page; import org.junit.Rule; import org.junit.Test; @@ -28,6 +30,8 @@ import org.keycloak.common.util.PemUtils; import org.keycloak.keys.Attributes; import org.keycloak.keys.KeyProvider; import org.keycloak.keys.RsaKeyProviderFactory; +import org.keycloak.representations.UserInfo; +import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.KeysMetadataRepresentation; import org.keycloak.representations.idm.RealmRepresentation; @@ -37,9 +41,13 @@ import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.AppPage.RequestType; import org.keycloak.testsuite.pages.LoginPage; +import org.keycloak.testsuite.util.KeycloakModelUtils; import org.keycloak.testsuite.util.OAuthClient; +import org.keycloak.testsuite.util.UserInfoClientUtil; +import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.core.Response; +import java.io.IOException; import java.security.KeyPair; import java.security.PublicKey; import java.util.List; @@ -65,6 +73,10 @@ public class KeyRotationTest extends AbstractKeycloakTest { public void addTestRealms(List testRealms) { RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class); testRealms.add(realm); + + ClientRepresentation confApp = KeycloakModelUtils.createClient(realm, "confidential-cli"); + confApp.setSecret("secret1"); + confApp.setServiceAccountsEnabled(Boolean.TRUE); } @Test @@ -114,6 +126,12 @@ public class KeyRotationTest extends AbstractKeycloakTest { assertTokenSignature(key1, response.getAccessToken()); assertTokenSignature(key1, response.getRefreshToken()); + // Userinfo with keys #1 + assertUserInfo(response.getAccessToken(), 200); + + // Token introspection with keys #1 + assertTokenIntrospection(response.getAccessToken(), true); + // Create keys #2 PublicKey key2 = createKeys2(); @@ -123,6 +141,12 @@ public class KeyRotationTest extends AbstractKeycloakTest { assertTokenSignature(key2, response.getAccessToken()); assertTokenSignature(key2, response.getRefreshToken()); + // Userinfo with keys #2 + assertUserInfo(response.getAccessToken(), 200); + + // Token introspection with keys #2 + assertTokenIntrospection(response.getAccessToken(), true); + // Drop key #1 dropKeys1(); @@ -131,9 +155,21 @@ public class KeyRotationTest extends AbstractKeycloakTest { assertTokenSignature(key2, response.getAccessToken()); assertTokenSignature(key2, response.getRefreshToken()); + // Userinfo with keys #1 dropped + assertUserInfo(response.getAccessToken(), 200); + + // Token introspection with keys #1 dropped + assertTokenIntrospection(response.getAccessToken(), true); + // Drop key #2 dropKeys2(); + // Userinfo with keys #2 dropped + assertUserInfo(response.getAccessToken(), 401); + + // Token introspection with keys #2 dropped + assertTokenIntrospection(response.getAccessToken(), false); + // Refresh token with keys #2 dropped - should fail as refresh token is signed with key #2 response = oauth.doRefreshTokenRequest(response.getRefreshToken(), "password"); assertEquals(400, response.getStatusCode()); @@ -232,5 +268,23 @@ public class KeyRotationTest extends AbstractKeycloakTest { throw new RuntimeException("Failed to find keys1"); } + private void assertUserInfo(String token, int expectedStatus) { + Response userInfoResponse = UserInfoClientUtil.executeUserInfoRequest_getMethod(ClientBuilder.newClient(), token); + assertEquals(expectedStatus, userInfoResponse.getStatus()); + userInfoResponse.close(); + } + + private void assertTokenIntrospection(String token, boolean expectActive) { + try { + String tokenResponse = oauth.introspectAccessTokenWithClientCredential("confidential-cli", "secret1", token); + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode jsonNode = objectMapper.readTree(tokenResponse); + assertEquals(expectActive, jsonNode.get("active").asBoolean()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaGeneratedKeyProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaGeneratedKeyProviderTest.java new file mode 100644 index 0000000000..324d99998e --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaGeneratedKeyProviderTest.java @@ -0,0 +1,203 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testsuite.keys; + +import org.jboss.arquillian.graphene.page.Page; +import org.junit.Rule; +import org.junit.Test; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.common.util.PemUtils; +import org.keycloak.keys.GeneratedRsaKeyProviderFactory; +import org.keycloak.keys.KeyMetadata; +import org.keycloak.keys.KeyProvider; +import org.keycloak.representations.idm.ComponentRepresentation; +import org.keycloak.representations.idm.ErrorRepresentation; +import org.keycloak.representations.idm.KeysMetadataRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.admin.ApiUtil; +import org.keycloak.testsuite.pages.AppPage; +import org.keycloak.testsuite.pages.LoginPage; + +import javax.ws.rs.core.Response; +import java.security.interfaces.RSAPublicKey; +import java.util.List; + +import static org.junit.Assert.*; +import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; + +/** + * @author Stian Thorgersen + */ +public class RsaGeneratedKeyProviderTest extends AbstractKeycloakTest { + + @Rule + public AssertEvents events = new AssertEvents(this); + + @Page + protected AppPage appPage; + + @Page + protected LoginPage loginPage; + + @Override + public void addTestRealms(List testRealms) { + RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class); + testRealms.add(realm); + } + + @Test + public void defaultKeysize() throws Exception { + long priority = System.currentTimeMillis(); + + ComponentRepresentation rep = createRep("valid", GeneratedRsaKeyProviderFactory.ID); + rep.setConfig(new MultivaluedHashMap<>()); + rep.getConfig().putSingle("priority", Long.toString(priority)); + + Response response = adminClient.realm("test").components().add(rep); + String id = ApiUtil.getCreatedId(response); + + ComponentRepresentation createdRep = adminClient.realm("test").components().component(id).toRepresentation(); + assertEquals(2, createdRep.getConfig().size()); + assertEquals(Long.toString(priority), createdRep.getConfig().getFirst("priority")); + assertEquals("2048", createdRep.getConfig().getFirst("keySize")); + + KeysMetadataRepresentation keys = adminClient.realm("test").keys().getKeyMetadata(); + + KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0); + + assertEquals(id, key.getProviderId()); + assertEquals(KeyMetadata.Type.RSA.name(), key.getType()); + assertEquals(priority, key.getProviderPriority()); + assertEquals(2048, ((RSAPublicKey) PemUtils.decodePublicKey(keys.getKeys().get(0).getPublicKey())).getModulus().bitLength()); + } + + @Test + public void largeKeysize() throws Exception { + long priority = System.currentTimeMillis(); + + ComponentRepresentation rep = createRep("valid", GeneratedRsaKeyProviderFactory.ID); + rep.setConfig(new MultivaluedHashMap<>()); + rep.getConfig().putSingle("priority", Long.toString(priority)); + rep.getConfig().putSingle("keySize", "4096"); + + Response response = adminClient.realm("test").components().add(rep); + String id = ApiUtil.getCreatedId(response); + + ComponentRepresentation createdRep = adminClient.realm("test").components().component(id).toRepresentation(); + assertEquals(2, createdRep.getConfig().size()); + assertEquals("4096", createdRep.getConfig().getFirst("keySize")); + + KeysMetadataRepresentation keys = adminClient.realm("test").keys().getKeyMetadata(); + + KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0); + + assertEquals(id, key.getProviderId()); + assertEquals(KeyMetadata.Type.RSA.name(), key.getType()); + assertEquals(priority, key.getProviderPriority()); + assertEquals(4096, ((RSAPublicKey) PemUtils.decodePublicKey(keys.getKeys().get(0).getPublicKey())).getModulus().bitLength()); + } + + @Test + public void updatePriority() throws Exception { + long priority = System.currentTimeMillis(); + + ComponentRepresentation rep = createRep("valid", GeneratedRsaKeyProviderFactory.ID); + rep.setConfig(new MultivaluedHashMap<>()); + rep.getConfig().putSingle("priority", Long.toString(priority)); + + Response response = adminClient.realm("test").components().add(rep); + String id = ApiUtil.getCreatedId(response); + + KeysMetadataRepresentation keys = adminClient.realm("test").keys().getKeyMetadata(); + + String publicKey = keys.getKeys().get(0).getPublicKey(); + + ComponentRepresentation createdRep = adminClient.realm("test").components().component(id).toRepresentation(); + + priority += 1000; + + createdRep.getConfig().putSingle("priority", Long.toString(priority)); + adminClient.realm("test").components().component(id).update(createdRep); + + keys = adminClient.realm("test").keys().getKeyMetadata(); + + String publicKey2 = keys.getKeys().get(0).getPublicKey(); + + assertEquals(publicKey, publicKey2); + } + + @Test + public void updateKeysize() throws Exception { + long priority = System.currentTimeMillis(); + + ComponentRepresentation rep = createRep("valid", GeneratedRsaKeyProviderFactory.ID); + rep.setConfig(new MultivaluedHashMap<>()); + rep.getConfig().putSingle("priority", Long.toString(priority)); + + Response response = adminClient.realm("test").components().add(rep); + String id = ApiUtil.getCreatedId(response); + + KeysMetadataRepresentation keys = adminClient.realm("test").keys().getKeyMetadata(); + + String publicKey = keys.getKeys().get(0).getPublicKey(); + + ComponentRepresentation createdRep = adminClient.realm("test").components().component(id).toRepresentation(); + createdRep.getConfig().putSingle("keySize", "4096"); + adminClient.realm("test").components().component(id).update(createdRep); + + keys = adminClient.realm("test").keys().getKeyMetadata(); + + String publicKey2 = keys.getKeys().get(0).getPublicKey(); + + assertNotEquals(publicKey, publicKey2); + assertEquals(2048, ((RSAPublicKey) PemUtils.decodePublicKey(publicKey)).getModulus().bitLength()); + assertEquals(4096, ((RSAPublicKey) PemUtils.decodePublicKey(publicKey2)).getModulus().bitLength()); + } + + @Test + public void invalidKeysize() throws Exception { + ComponentRepresentation rep = createRep("invalid", GeneratedRsaKeyProviderFactory.ID); + rep.getConfig().putSingle("keySize", "1234"); + + Response response = adminClient.realm("test").components().add(rep); + assertErrror(response, "Keysize should be 1024, 2048 or 4096"); + } + + protected void assertErrror(Response response, String error) { + if (!response.hasEntity()) { + fail("No error message set"); + } + + ErrorRepresentation errorRepresentation = response.readEntity(ErrorRepresentation.class); + assertEquals(error, errorRepresentation.getErrorMessage()); + } + + protected ComponentRepresentation createRep(String name, String providerId) { + ComponentRepresentation rep = new ComponentRepresentation(); + rep.setName(name); + rep.setParentId("test"); + rep.setProviderId(providerId); + rep.setProviderType(KeyProvider.class.getName()); + rep.setConfig(new MultivaluedHashMap<>()); + return rep; + } + +} + diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaKeyProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaKeyProviderTest.java index 649c6622a8..65cda1a98e 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaKeyProviderTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaKeyProviderTest.java @@ -27,6 +27,7 @@ import org.keycloak.common.util.KeyUtils; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.PemUtils; import org.keycloak.keys.Attributes; +import org.keycloak.keys.GeneratedRsaKeyProviderFactory; import org.keycloak.keys.KeyMetadata; import org.keycloak.keys.KeyProvider; import org.keycloak.keys.RsaKeyProvider; @@ -76,12 +77,14 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest { @Test public void privateKeyOnly() throws Exception { + long priority = System.currentTimeMillis(); + KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048); String kid = KeyUtils.createKeyId(keyPair.getPublic()); ComponentRepresentation rep = createRep("valid", RsaKeyProviderFactory.ID); rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate())); - rep.getConfig().putSingle(Attributes.PRIORITY_KEY, "1000"); + rep.getConfig().putSingle(Attributes.PRIORITY_KEY, Long.toString(priority)); Response response = adminClient.realm("test").components().add(rep); String id = ApiUtil.getCreatedId(response); @@ -100,7 +103,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest { assertEquals(id, key.getProviderId()); assertEquals(KeyMetadata.Type.RSA.name(), key.getType()); - assertEquals(1000l, key.getProviderPriority()); + assertEquals(priority, key.getProviderPriority()); assertEquals(kid, key.getKid()); assertEquals(PemUtils.encodeKey(keyPair.getPublic()), keys.getKeys().get(0).getPublicKey()); assertEquals(keyPair.getPublic(), PemUtils.decodeCertificate(key.getCertificate()).getPublicKey()); @@ -108,6 +111,8 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest { @Test public void keyAndCertificate() throws Exception { + long priority = System.currentTimeMillis(); + KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048); Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, "test"); String certificatePem = PemUtils.encodeCertificate(certificate); @@ -115,7 +120,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest { ComponentRepresentation rep = createRep("valid", RsaKeyProviderFactory.ID); rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate())); rep.getConfig().putSingle(Attributes.CERTIFICATE_KEY, certificatePem); - rep.getConfig().putSingle(Attributes.PRIORITY_KEY, "1000"); + rep.getConfig().putSingle(Attributes.PRIORITY_KEY, Long.toString(priority)); Response response = adminClient.realm("test").components().add(rep); String id = ApiUtil.getCreatedId(response); diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/org/keycloak/testsuite/keys/keystore.jks b/testsuite/integration-arquillian/tests/base/src/test/resources/org/keycloak/testsuite/keys/keystore.jks new file mode 100644 index 0000000000..ad54a7bcb7 Binary files /dev/null and b/testsuite/integration-arquillian/tests/base/src/test/resources/org/keycloak/testsuite/keys/keystore.jks differ