From ccfae82b77aef81cf9baa01a654383e90cfc80e9 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 26 Feb 2016 16:57:12 +0100 Subject: [PATCH] KEYCLOAK-1040 Allow import of realm keys (like we do for SAML) --- .../java/org/keycloak/KeyPairVerifier.java | 59 +++++++++++++ .../org/keycloak/KeyPairVerifierTest.java | 69 ++++++++++++++++ .../META-INF/jpa-changelog-1.9.1.xml | 23 ++++++ .../META-INF/jpa-changelog-master.xml | 1 + .../models/utils/RepresentationToModel.java | 10 +++ .../resources/admin/RealmAdminResource.java | 18 +++- .../keycloak/testsuite/admin/RealmTest.java | 82 +++++++++++++++++++ .../src/test/resources/keys/certificate.pem | 21 +++++ .../src/test/resources/keys/private2048.pem | 27 ++++++ .../src/test/resources/keys/private4096.pem | 51 ++++++++++++ .../src/test/resources/keys/public2048.pem | 9 ++ .../src/test/resources/keys/public4096.pem | 14 ++++ .../messages/admin-messages_en.properties | 1 + .../admin/resources/js/controllers/realm.js | 45 +++++++++- .../admin/resources/partials/realm-keys.html | 19 ++++- 15 files changed, 443 insertions(+), 6 deletions(-) create mode 100644 core/src/main/java/org/keycloak/KeyPairVerifier.java create mode 100644 core/src/test/java/org/keycloak/KeyPairVerifierTest.java create mode 100755 model/jpa/src/main/resources/META-INF/jpa-changelog-1.9.1.xml create mode 100644 testsuite/integration/src/test/resources/keys/certificate.pem create mode 100644 testsuite/integration/src/test/resources/keys/private2048.pem create mode 100644 testsuite/integration/src/test/resources/keys/private4096.pem create mode 100644 testsuite/integration/src/test/resources/keys/public2048.pem create mode 100644 testsuite/integration/src/test/resources/keys/public4096.pem diff --git a/core/src/main/java/org/keycloak/KeyPairVerifier.java b/core/src/main/java/org/keycloak/KeyPairVerifier.java new file mode 100644 index 0000000000..c8b6dce612 --- /dev/null +++ b/core/src/main/java/org/keycloak/KeyPairVerifier.java @@ -0,0 +1,59 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak; + +import org.keycloak.common.VerificationException; +import org.keycloak.common.util.PemUtils; +import org.keycloak.jose.jws.JWSBuilder; +import org.keycloak.jose.jws.JWSInput; +import org.keycloak.jose.jws.crypto.RSAProvider; + +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * @author Stian Thorgersen + */ +public class KeyPairVerifier { + + public static void verify(String privateKeyPem, String publicKeyPem) throws VerificationException { + PrivateKey privateKey; + try { + privateKey = PemUtils.decodePrivateKey(privateKeyPem); + } catch (Exception e) { + throw new VerificationException("Failed to decode private key"); + } + + PublicKey publicKey; + try { + publicKey = PemUtils.decodePublicKey(publicKeyPem); + } catch (Exception e) { + throw new VerificationException("Failed to decode public key"); + } + + try { + String jws = new JWSBuilder().content("content".getBytes()).rsa256(privateKey); + if (!RSAProvider.verify(new JWSInput(jws), publicKey)) { + throw new VerificationException("Keys don't match"); + } + } catch (Exception e) { + throw new VerificationException("Keys don't match"); + } + } + +} diff --git a/core/src/test/java/org/keycloak/KeyPairVerifierTest.java b/core/src/test/java/org/keycloak/KeyPairVerifierTest.java new file mode 100644 index 0000000000..da76a611b9 --- /dev/null +++ b/core/src/test/java/org/keycloak/KeyPairVerifierTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak; + +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.common.VerificationException; + +/** + * @author Stian Thorgersen + */ +public class KeyPairVerifierTest { + + String privateKey1 = "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y="; + String publicKey1 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"; + + String privateKey2048 = "-----BEGIN RSA PRIVATE KEY-----\n" + "MIIEpQIBAAKCAQEA4V3MpOnuKsdBbR1UzNjK9o5meEMQ4s5Vpykhv1DpqTilKOiE\n" + + "H7VQ/XtjNxw0yjnFBilCnpK6yN9mDEHbBEzaRjtdrgVhkIejiaXFBP5MBhUQ5l9u\n" + "8E3IZC3E8pwDjVF0Z9u0R4lGeUg2k6O+NKumqIvxoLCTuG0zf53bctGsRd57LuFi\n" + + "pgCkNyxvscOhulsbEMYrLwlb5bMGgx9v+RCnwvunNEb7RK+5pzP+iH1MRejRsX+U\n" + "7h9zHRn2gQhIl7SzG9GXebuPWr4KKwfMHWy0PEuQrsfWRXm9/dTEavbfNkv5E53z\n" + + "WXjWyf93ezkVhBX0YoXmf6UO7PAlvsrjno3TuwIDAQABAoIBAQC5iCAOcCtLemhp\n" + "bOlADwXgPtErFoNTROyMxjbrKrCCSIjniawj8oAvfiHq38Sx6ydBcDxREZjF/+wi\n" + + "ESE+hAp6ISt5NSLh+lhu3FK7TqLFqxgTn+NT36Umm+t0k231LGa5jcz3y5KCDCoq\n" + "F3ZiJCH6xeLxGA00mmn4GLvt5aF+jiO80ICGs4iUg99IoXhc5u/VU0hB5J78BinW\n" + + "inkCABuBNkDLgIqc9BoH4L5MOx3zDqzmHffeq9+2V4X7NiD5QyiyWtABaQpEIY5k\n" + "R48RTno6xN3hvG48/DwkO2gABSLQ/OJd3Hupv4wlmmSc1xo93CaV44hq2i2GsU1i\n" + + "m6d3xDW5AoGBAPCfkvPkqr88xg+8Cu3G/3GFpUsQ0VEme+8dIjXMTJHa13K7xaRh\n" + "GHCVg4a8oHJ/P/vNSwvPyR71iRX4csqkKSaprvJk8vxbU539unmHWKkfUHrywQlz\n" + + "q4OuXOjOdvILLOTsu3/+k6vAIE6SZJiDmf2eGxi9Qbm5rlxE3h3HRAKfAoGBAO/E\n" + "ogHV86LmnJTJbx1hP3IfRHk0qaiSj35ljlAz+3v6GN/KSUYCWTtp2GjRIKY3qQ8I\n" + + "7l+PVTFg3SY7cPq2C9TE+6xroiWkUd2JldPLYSxpWpFNYlo709SzmLquDho+fwJC\n" + "nAxoxKghsXJarz7TRfNyFqDXscS6oQLurU9P5lVlAoGBAJh1QvLtS5Jnu0Z06qfF\n" + + "kkwnVZe+TCGStKvIVciobUts0V2Mw6lnK8kJspBIK5DgN3YfmREe0lufTwBwrqre\n" + "YIRytro2ZA6o/s332ZLuwqpFgQSlktGeTGnerFeFma+6jPNvW025y27jCJVABCTu\n" + + "HT+oUZrXLzGyCFvF9sX/X4QZAoGBAICap4r0h0nJCBOGN+M6Vh2QR9n7NUUF15Gk\n" + "R0EdoLZO3yiqB8NVXydPDpSqFykQkc1OrQz0hG2H1xa6q07OdmoZfiRtVvt5t69s\n" + + "LMD9RZHcsIdfSnG7xVNBQZpf4ZCSFO3RbIH7b//+kn8TxQudptd9SkXba65prBM2\n" + "kh8IbDNBAoGAVsKvkruH7RK7CimDSWcdAKvHARqkjs/PoeKEEY8Yu6zf0Z9TQM5l\n" + + "uC9EwBamYcSusWRcdcz+9HYG58XFnmXq+3EUuFbJ+Ljb8YWBgePjSHDoS/6+/+zq\n" + "B1b5uQp/jYFbYQl50UPRPTF+ul1eQoy7F43Ngj3/5cDRarFZe3ZTzZo=\n" + + "-----END RSA PRIVATE KEY-----"; + String publicKey2048 = "-----BEGIN PUBLIC KEY-----\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4V3MpOnuKsdBbR1UzNjK\n" + + "9o5meEMQ4s5Vpykhv1DpqTilKOiEH7VQ/XtjNxw0yjnFBilCnpK6yN9mDEHbBEza\n" + "RjtdrgVhkIejiaXFBP5MBhUQ5l9u8E3IZC3E8pwDjVF0Z9u0R4lGeUg2k6O+NKum\n" + + "qIvxoLCTuG0zf53bctGsRd57LuFipgCkNyxvscOhulsbEMYrLwlb5bMGgx9v+RCn\n" + "wvunNEb7RK+5pzP+iH1MRejRsX+U7h9zHRn2gQhIl7SzG9GXebuPWr4KKwfMHWy0\n" + + "PEuQrsfWRXm9/dTEavbfNkv5E53zWXjWyf93ezkVhBX0YoXmf6UO7PAlvsrjno3T\n" + "uwIDAQAB\n" + "-----END PUBLIC KEY-----"; + + @Test + public void verify() throws Exception { + KeyPairVerifier.verify(privateKey1, publicKey1); + KeyPairVerifier.verify(privateKey2048, publicKey2048); + + try { + KeyPairVerifier.verify(privateKey1, publicKey2048); + Assert.fail("Expected VerificationException"); + } catch (VerificationException e) { + } + + try { + KeyPairVerifier.verify(privateKey2048, publicKey1); + Assert.fail("Expected VerificationException"); + } catch (VerificationException e) { + } + } + +} diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-1.9.1.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-1.9.1.xml new file mode 100755 index 0000000000..63474a1438 --- /dev/null +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-1.9.1.xml @@ -0,0 +1,23 @@ + + + + + + + + \ No newline at end of file diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml index 6a4d1dcbbf..c107b8ef58 100755 --- a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml @@ -30,4 +30,5 @@ + diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index d349f4e02e..970fd7eede 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -695,6 +695,16 @@ public class RepresentationToModel { if ("GENERATE".equals(rep.getPublicKey())) { KeycloakModelUtils.generateRealmKeys(realm); + } else { + if (rep.getPrivateKey() != null && rep.getPublicKey() != null) { + realm.setPrivateKeyPem(rep.getPrivateKey()); + realm.setPublicKeyPem(rep.getPublicKey()); + realm.setCodeSecret(KeycloakModelUtils.generateCodeSecret()); + } + + if (rep.getCertificate() != null) { + realm.setCertificatePem(rep.getCertificate()); + } } if(rep.isInternationalizationEnabled() != null){ diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index 0c9f4ce341..b434cdfe04 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -20,7 +20,10 @@ import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.BadRequestException; import org.jboss.resteasy.spi.NotFoundException; import org.jboss.resteasy.spi.ResteasyProviderFactory; +import org.keycloak.KeyPairVerifier; import org.keycloak.common.ClientConnection; +import org.keycloak.common.VerificationException; +import org.keycloak.common.util.PemUtils; import org.keycloak.events.Event; import org.keycloak.events.EventQuery; import org.keycloak.events.EventStoreProvider; @@ -30,6 +33,8 @@ import org.keycloak.events.admin.AdminEventQuery; import org.keycloak.events.admin.OperationType; import org.keycloak.exportimport.ClientDescriptionConverter; import org.keycloak.exportimport.ClientDescriptionConverterFactory; +import org.keycloak.jose.jws.JWSBuilder; +import org.keycloak.jose.jws.JWSInput; import org.keycloak.models.ClientModel; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; @@ -74,8 +79,11 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; +import java.security.PrivateKey; +import java.security.PublicKey; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -236,6 +244,14 @@ public class RealmAdminResource { logger.debug("updating realm: " + realm.getName()); try { + if (!"GENERATE".equals(rep.getPublicKey()) && (rep.getPrivateKey() != null && rep.getPublicKey() != null)) { + try { + KeyPairVerifier.verify(rep.getPrivateKey(), rep.getPublicKey()); + } catch (VerificationException e) { + return ErrorResponse.error(e.getMessage(), Status.BAD_REQUEST); + } + } + RepresentationToModel.updateRealm(rep, realm); // Refresh periodic sync tasks for configured federationProviders @@ -253,7 +269,7 @@ public class RealmAdminResource { throw e; } catch (Exception e) { logger.error(e.getMessage(), e); - return ErrorResponse.error("Failed to update " + rep.getRealm() + " Realm.", Response.Status.INTERNAL_SERVER_ERROR); + return ErrorResponse.error("Failed to update realm", Response.Status.INTERNAL_SERVER_ERROR); } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java index 156835adba..511e6b58e2 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java @@ -25,6 +25,7 @@ import org.keycloak.admin.client.resource.ServerInfoResource; import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; @@ -32,6 +33,7 @@ import org.keycloak.services.managers.RealmManager; import org.keycloak.testsuite.KeycloakServer; import org.keycloak.util.JsonSerialization; +import javax.ws.rs.BadRequestException; import javax.ws.rs.NotFoundException; import java.io.IOException; import java.util.Collections; @@ -51,6 +53,10 @@ import static org.junit.Assert.fail; */ public class RealmTest extends AbstractClientTest { + public static final String PRIVATE_KEY = "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y="; + public static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"; + public static final String CERTIFICATE = "MIICsTCCAZkCBgFTLB5bhDANBgkqhkiG9w0BAQsFADAcMRowGAYDVQQDDBFhZG1pbi1jbGllbnQtdGVzdDAeFw0xNjAyMjkwODIwMDBaFw0yNjAyMjgwODIxNDBaMBwxGjAYBgNVBAMMEWFkbWluLWNsaWVudC10ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAquzJtpAlpTFnJzILjTOHW+SOWav1eIsCtlAqiFTvBskbod6b4BtVaR3FVrQm8rFiwDOIEWT3IG3ZIz0LKYxnqvuffyLHGHjiroqrR63kY9Wa9B790lSEWVaGeNOMnKleqKu5QUNfL3wVebUh/C/QfxZ29R1EIbxNe2ThN8yuIca8Ltn43D5VlyatptojffxpCYiYqAmIwQDaq1um2cQ+4rPBLxC5jM9UBvYOMUP4u0caNSaPI1o9lHVKgTtWcdQzUeMmAGsnLV26XGhA/OwRduUxksumR1kh/KSqowasjgSrpVqtF/uo5TY57s7drD+zKG58cdHLreclB9AQNvNwZwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBh4iwg8GnadeQP52pV5vKJ4Z8A1R2aYCzoW7Lc3FI/pXWX9Af5dKILX5O2j/daamPS+WtDWxIuwvZC5drrkvJn/r8e4KstnXQzPQggIJbI9v3wfIX3VlFvwvZVGiuE5PSLSWb0L57PEojZVpIU5bLchq4yRSD2zK4dWX8Y6I/D40a74KDvPOlEL8405/T1iW7ytKT9awNJW04N91owoI+kdUL+DMnnGzIxDAoYAeZI/1vcwoaH24zyTLGItkzpKxqLOdB05cnxn5jCWY2Hyd1zqtRkadhgZaqu4lcDHAHEMDp6dEjLZW8ym8bnlto+MD2y//CsyPCzyCLlA726vrli"; + @Test public void getRealms() { List realms = keycloak.realms().findAll(); @@ -331,4 +337,80 @@ public class RealmTest extends AbstractClientTest { } + @Test + public void uploadRealmKeys() throws Exception { + String originalPublicKey = realm.toRepresentation().getPublicKey(); + + RealmRepresentation rep = new RealmRepresentation(); + rep.setPrivateKey("INVALID"); + rep.setPublicKey(PUBLIC_KEY); + + try { + realm.update(rep); + fail("Expected BadRequestException"); + } catch (BadRequestException e) { + } + + rep.setPrivateKey(PRIVATE_KEY); + rep.setPublicKey("INVALID"); + + try { + realm.update(rep); + fail("Expected BadRequestException"); + } catch (BadRequestException e) { + } + + assertEquals(originalPublicKey, realm.toRepresentation().getPublicKey()); + + rep.setPublicKey(PUBLIC_KEY); + realm.update(rep); + + assertEquals(PUBLIC_KEY, rep.getPublicKey()); + + String privateKey2048 = IOUtils.toString(getClass().getResourceAsStream("/keys/private2048.pem")); + String publicKey2048 = IOUtils.toString(getClass().getResourceAsStream("/keys/public2048.pem")); + + rep.setPrivateKey(privateKey2048); + + try { + realm.update(rep); + fail("Expected BadRequestException"); + } catch (BadRequestException e) { + } + + assertEquals(PUBLIC_KEY, realm.toRepresentation().getPublicKey()); + + rep.setPublicKey(publicKey2048); + + realm.update(rep); + + assertEquals(publicKey2048, realm.toRepresentation().getPublicKey()); + + String privateKey4096 = IOUtils.toString(getClass().getResourceAsStream("/keys/private4096.pem")); + String publicKey4096 = IOUtils.toString(getClass().getResourceAsStream("/keys/public4096.pem")); + rep.setPrivateKey(privateKey4096); + rep.setPublicKey(publicKey4096); + + realm.update(rep); + + assertEquals(publicKey4096, realm.toRepresentation().getPublicKey()); + } + + @Test + public void uploadCertificate() throws IOException { + RealmRepresentation rep = new RealmRepresentation(); + rep.setCertificate(CERTIFICATE); + + realm.update(rep); + + assertEquals(CERTIFICATE, rep.getCertificate()); + + String certificate = IOUtils.toString(getClass().getResourceAsStream("/keys/certificate.pem")); + rep.setCertificate(certificate); + + realm.update(rep); + + assertEquals(certificate, rep.getCertificate()); + } + } diff --git a/testsuite/integration/src/test/resources/keys/certificate.pem b/testsuite/integration/src/test/resources/keys/certificate.pem new file mode 100644 index 0000000000..a526c93da5 --- /dev/null +++ b/testsuite/integration/src/test/resources/keys/certificate.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDXTCCAkWgAwIBAgIJAIzE3vQp7EQWMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTYwMjI5MDgzMDU0WhcNNDMwNzE2MDgzMDU0WjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAp1+GzdEkt2FZbISXYO12503FL6Oh8s4+tJ2fE66N8IezhugP8xiySDfW +TEMaO5Z2TaTnQQoF9SSZ9Edq1GPxpBX0cdkCOBopEGdlb3hUYDeMaDMs18KGemUc +Fj+CWB5VVcbmWMJ36WCz7FC+Oe38tmujR1AJpJL3pwqazyWIZzPqX8rW+rrNPGKP +C96oBPZMb4RJWivLBJi/o5MGSpo1sJNtxyF4zUUI00LX0wZAV1HH1XErd1Vz41on +nmB+tj9nevVRR4rDV280IELp9Ud0PIb3w843uJtwfSAwVG0pT6hv1VBDrBxTS08N +dPU8CtkQAXzCCr8nqfAbUFOhcWRQgQIDAQABo1AwTjAdBgNVHQ4EFgQUFE+uUZAI +n57ArEylqhCmHkAenTEwHwYDVR0jBBgwFoAUFE+uUZAIn57ArEylqhCmHkAenTEw +DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEApkgD3OCtw3+sk7GR1YaJ +xNd8HT+fxXmnLqGnCQWX8lRIg5vj1PDMRev6vlIK3JfQV3zajcpKFfpy96klWsJy +ZLYBVW2QOtMzDdQ9I8dS4Pn/SJ/Vo/M/ucfY4ttcuUL3oQCrI/c/u9tcamGMfbwd +658MlXrUvt4B6qXY5AbgUvYR25P86uw7hSFMq5tQftNQsLbOh2FEeIiKhpgI7w8S +SPajaWjUXsfHc5H7f9MciE2NS1Vd3AViGrVWP1rgQ1Iv0UyQVQrnjmIs12ENJmTd +5lDqra5FJhaO7+RUG6er8n8HwXzhHkPmezGqtxWKikjitqvDY9prB3omJSa4Led+ +AQ== +-----END CERTIFICATE----- diff --git a/testsuite/integration/src/test/resources/keys/private2048.pem b/testsuite/integration/src/test/resources/keys/private2048.pem new file mode 100644 index 0000000000..e2baad38cc --- /dev/null +++ b/testsuite/integration/src/test/resources/keys/private2048.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEApnZ/E2BUULHjsRiSnEgZ4vGe15BRqZPdkHR+NcvVYpThc7Jq +Y6nZrdrwO9sOjlMC5e2Q18Fypi4KbJpGSe9r0DPgcbPsHSoe2xFO3M8XBE0DyoRb +laQFhe6p/sj3ak32k2zn+fMZUmlx/MTNQh1ICki7So0NDCBXt8XGZNnEyvKeXOUZ +P5qicP9KxVAQiWJvlkaTjc8rrRTmf+HWw/QfgQC0tzBRpa7T+RpW9O+rnWfOaNfT +kTb9itIc+ZOa2Z4iidZ7+ifMOp9cNT641Wb6iYqJ2ufqY+msxI54tYM1tPgGS7r4 +SnCwmnqTaO383wXUl8TQ7qStmAWIepV3nNyuAQIDAQABAoIBAEj90rDrX21W43Fn +RfpTP06dBjqdpMFH/jJ2clUigPnOMKGrzSzQcIvkYczNPC+6RJ4PsqB4yc4GiDmg +2EtZOZw88yDIdTNAofELQNpf0Ebpgk0OBp6yIl3dDhuTgbHSZ9mzOnEGYMcbR4k/ +voVME6e2xrFk8iCsGeqSRXE5cCpQzXLFQGRrGGoJH8hggteyQhei39pxo8IM87A0 +ptdZj8YJ0YUUpztyshR5KAzZeJPjKRXE1wPiZoqDzQ0OCC+k9IGCeJIAAGVB1Zs7 +4ga4gEkczYfSbhRRGl7Uls4XJHL1pNsU75sRpQZGuGm9as6PprYJiDH5Eoz+EdzO ++oxs0FUCgYEA1NFbnyjlTEB9liVAMSd+BNRL7c78+Tsbfn/y6b1pEcSZ5+R79tNw +NTAmk0GSrW64MRQS2gYe871w9QBwpSlNoGMN5Zjzl8WIZ+AFzTCVnEyBt0DOTj/w +ZU2/o56C+/CYj6kUjP88HRIX/Grr7rY20uGcDKVLFVeYCBGUI/m0WOsCgYEAyD1F +TLZea00ZBl0xb/ju0KKGHxw63BldfoKCXjTarz1M7Nl9vBdGuOBH+H3a2wSPhJfb +7Le9LY+tU53aCJql5xshsDNtduTMdHPamKXkHH038Tbn1vb8HWPzAzYcAY3oAPVJ +OPPWtTmVeitl0Lo5kPYFbx4yBykOOAokLE/qGcMCgYBZu4CnRkYQday+TyyWzTEM +djshpUHzEGISX36b4ZpYvI2sQiGmvBY2xvus4VwoNmQBhZZBSY1pdjoXg7z7VsP9 +WWa1pV0oZEiUi9fGYbLjeTrEetXCFqGVBUhFhAN0mUiqYj9hCAlftI5ahva96ySI +nEoA5v0WnZ1j4Y2V8aaCSwKBgA2bBCtrNM0rpuikymgmTOvGL0DL5T/xRUYETiFi +i/1eN+zb9kwidL65ForO2mEJVUsYGmxiK6t92LQWxKrS/zTNxiM3y1dJwo6jFJZM +p0w8QeHU7jnP/F9u3CM6uPpuDvaJtBj3kH4t1HdBnaBqFuE/pizfq1yLMJkkL0MH +hwuLAoGAEwOTV+mkM6Fv+JGDwRkfFlz95XfQsTXT3xBgGy9sGmtn2b4tPCzO/Fxd +eLD9dMeI8M3yB4Vc1gRhK8jmmgvcYhsyLn+DreLGWnwQjQn+vAMCpkcPMGJjp3bz +UooIHTCHq7aNirxPrzgi+F5PcGR074qPFrdVaL031CPTAGmMqzE= +-----END RSA PRIVATE KEY----- diff --git a/testsuite/integration/src/test/resources/keys/private4096.pem b/testsuite/integration/src/test/resources/keys/private4096.pem new file mode 100644 index 0000000000..e854f459c7 --- /dev/null +++ b/testsuite/integration/src/test/resources/keys/private4096.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/testsuite/integration/src/test/resources/keys/public2048.pem b/testsuite/integration/src/test/resources/keys/public2048.pem new file mode 100644 index 0000000000..565df1e32f --- /dev/null +++ b/testsuite/integration/src/test/resources/keys/public2048.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApnZ/E2BUULHjsRiSnEgZ +4vGe15BRqZPdkHR+NcvVYpThc7JqY6nZrdrwO9sOjlMC5e2Q18Fypi4KbJpGSe9r +0DPgcbPsHSoe2xFO3M8XBE0DyoRblaQFhe6p/sj3ak32k2zn+fMZUmlx/MTNQh1I +Cki7So0NDCBXt8XGZNnEyvKeXOUZP5qicP9KxVAQiWJvlkaTjc8rrRTmf+HWw/Qf +gQC0tzBRpa7T+RpW9O+rnWfOaNfTkTb9itIc+ZOa2Z4iidZ7+ifMOp9cNT641Wb6 +iYqJ2ufqY+msxI54tYM1tPgGS7r4SnCwmnqTaO383wXUl8TQ7qStmAWIepV3nNyu +AQIDAQAB +-----END PUBLIC KEY----- diff --git a/testsuite/integration/src/test/resources/keys/public4096.pem b/testsuite/integration/src/test/resources/keys/public4096.pem new file mode 100644 index 0000000000..1d1fa1be0f --- /dev/null +++ b/testsuite/integration/src/test/resources/keys/public4096.pem @@ -0,0 +1,14 @@ +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAni4pAISeCrzLSEuTJPx5 +1Uwh0LIQuNHRTah1UuWk5NP4W1ctl1PuCoddtZNiPL2tnEw0Js1m7vlI3+miynNy +8ze2LCtaA98oPUVsD9C13hKH3nQ1Zf/IYCzHMC7BAJ7pFCgo2TM0S2ic2eHyr+/v +Qd2ha8mnj6hzwJ3O9KaIINDLA1EucJNZDxmlbzJfap5mFH1Uj2RhDiYM4m8445fR +7QDv3Zhiv0HMP0dCaVnPot+jT6ar195XDDLSIrK+sc8SFLdlc4BlGitDyZviKVzI +5E4nn4hkHLBK6jHzNd+afJLPyZImt0N9NPj6y3h3gaGAVMWQpK2bgKEdF6417CPG +bcMuGsBbpmDQzl9T4EtcN/Pq741tyCEXUWORHOEU+bBiEO1RAWsah13LxhYQ38Yc +lTkr7BGZfQ4sfEZDKEknr7UuQw1eE7eZLmiAb+N72kxO5u50OtAmqUhw8LN5iQ/g +D5dcItsdi643nKTFUB+bEIqqWaXBvRsIn3BmNJiPrgUFJoMm94vrAC1WKjKU4QfE +RSD/SUuFL3FisP3c33VP1VX33rfM6PIWyffIx43XoSp7MwFL3zPvTNLBP33LIBPD +83ZT1Af46ky8TIA98CEkNNQdBOQEtrSsm0snYuku1xNQrdccw3jFvroVQGGPS4fL +b0ZveUzvWlTXfPwcF/I+UX8CAwEAAQ== +-----END PUBLIC KEY----- diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index 57836f55a2..3b4a9749fe 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -38,6 +38,7 @@ sslRequired.option.external=external requests sslRequired.option.none=none sslRequired.tooltip=Is HTTPS required? 'None' means HTTPS is not required for any client IP address. 'External requests' means localhost and private IP addresses can access without HTTPS. 'All requests' means HTTPS is required for all IP addresses. publicKey=Public key +privateKey=Private key gen-new-keys=Generate new keys certificate=Certificate host=Host diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js index c93e8c6929..94b550d704 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js @@ -990,8 +990,21 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http, }; }); -module.controller('RealmKeysDetailCtrl', function($scope, Realm, realm, $http, $location, Dialog, Notifications) { - $scope.realm = realm; +module.controller('RealmKeysDetailCtrl', function($scope, Realm, realm, $http, $route, $location, Dialog, Notifications) { + $scope.realm = angular.copy(realm); + $scope.enableUpload = false; + + $scope.$watch('realm', function () { + if (!angular.equals($scope.realm, realm)) { + if ($scope.realm.privateKey && $scope.realm.publicKey != realm.publicKey) { + $scope.enableUpload = true; + } else if ($scope.realm.certificate != realm.certificate) { + $scope.enableUpload = true; + } else { + $scope.enableUpload = false; + } + } + }, true); $scope.generate = function() { Dialog.confirmGenerateKeys($scope.realm.realm, 'realm', function() { @@ -1003,6 +1016,34 @@ module.controller('RealmKeysDetailCtrl', function($scope, Realm, realm, $http, $ }); }); }; + + $scope.cancel = function() { + $route.reload(); + } + + $scope.save = function() { + var title = 'Upload keys for realm'; + var msg = 'Are you sure you want to upload keys for ' + $scope.realm.realm + '?'; + var btns = { + ok: { + label: 'Upload Keys', + cssClass: 'btn btn-danger' + }, + cancel: { + label: 'Cancel', + cssClass: 'btn btn-default' + } + }; + + Dialog.open(title, msg, btns, function() { + Realm.update($scope.realm, function () { + Notifications.success('Keys uploaded for realm.'); + Realm.get({ id : realm.realm }, function(updated) { + $scope.realm = updated; + }) + }); + }); + }; }); module.controller('RealmSessionStatsCtrl', function($scope, realm, stats, RealmClientSessionStats, RealmLogoutAll, Notifications) { diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys.html index 93301af67a..b8a16c9e26 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys.html @@ -3,26 +3,39 @@
+
+ + +
+ +
+
+ kc-select-action="click" data-ng-model="realm.publicKey">
- +
- + +
+
+
+
+ +