diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js index 03dce9969f..6d68bac310 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js @@ -299,7 +299,7 @@ module.controller('ApplicationDetailCtrl', function($scope, realm, application, $scope.create = !application.name; $scope.samlServerSignature = false; $scope.samlClientSignature = false; - $scope.samlServerEncrypt = false; + $scope.samlEncrypt = false; if (!$scope.create) { if (!application.attributes) { application.attributes = {}; @@ -335,9 +335,9 @@ module.controller('ApplicationDetailCtrl', function($scope, realm, application, $scope.samlClientSignature = true; } } - if ($scope.application.attributes["samlServerEncrypt"]) { - if ($scope.application.attributes["samlServerEncrypt"] == "true") { - $scope.samlServerEncrypt = true; + if ($scope.application.attributes["samlEncrypt"]) { + if ($scope.application.attributes["samlEncrypt"] == "true") { + $scope.samlEncrypt = true; } } @@ -406,10 +406,10 @@ module.controller('ApplicationDetailCtrl', function($scope, realm, application, $scope.application.attributes["samlClientSignature"] = "false"; } - if ($scope.samlServerEncrypt == true) { - $scope.application.attributes["samlServerEncrypt"] = "true"; + if ($scope.samlEncrypt == true) { + $scope.application.attributes["samlEncrypt"] = "true"; } else { - $scope.application.attributes["samlServerEncrypt"] = "false"; + $scope.application.attributes["samlEncrypt"] = "false"; } diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-detail.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-detail.html index 00eec89310..d137708706 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-detail.html +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-detail.html @@ -57,18 +57,18 @@
- +
- +
- +
- +
- +
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SALM2PostBindingLoginResponseBuilder.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SALM2PostBindingLoginResponseBuilder.java index c65cbf33ed..e067204da0 100755 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SALM2PostBindingLoginResponseBuilder.java +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SALM2PostBindingLoginResponseBuilder.java @@ -174,9 +174,8 @@ public class SALM2PostBindingLoginResponseBuilder extends SAML2PostBindingBuilde throw logger.samlAssertionMarshallError(e); } - if (signed) { - signDocument(samlResponseDocument); - } + encryptAndSign(samlResponseDocument); + return samlResponseDocument; } diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2PostBindingBuilder.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2PostBindingBuilder.java index a1b105c1af..07cd939190 100755 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2PostBindingBuilder.java +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2PostBindingBuilder.java @@ -1,15 +1,23 @@ package org.keycloak.protocol.saml; import org.picketlink.common.constants.GeneralConstants; +import org.picketlink.common.constants.JBossSAMLConstants; +import org.picketlink.common.constants.JBossSAMLURIConstants; import org.picketlink.common.exceptions.ConfigurationException; import org.picketlink.common.exceptions.ProcessingException; import org.picketlink.common.util.DocumentUtil; +import org.picketlink.identity.federation.core.util.XMLEncryptionUtil; +import org.picketlink.identity.federation.core.wstrust.WSTrustUtil; import org.picketlink.identity.federation.web.util.PostBindingUtil; import org.w3c.dom.Document; +import org.w3c.dom.Node; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; import javax.ws.rs.core.CacheControl; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import javax.xml.namespace.QName; import java.io.IOException; import java.security.KeyPair; import java.security.PrivateKey; @@ -31,6 +39,10 @@ public class SAML2PostBindingBuilder { protected String relayState; protected String destination; protected String responseIssuer; + protected int encryptionKeySize = 128; + protected PublicKey encryptionPublicKey; + protected String encryptionAlgorithm = "AES"; + protected boolean encrypt; public T sign(KeyPair keyPair) { this.signingKeyPair = keyPair; @@ -51,6 +63,29 @@ public class SAML2PostBindingBuilder { return (T)this; } + public T sign(PrivateKey privateKey, PublicKey publicKey, X509Certificate cert) { + this.signingKeyPair = new KeyPair(publicKey, privateKey); + this.signingCertificate = cert; + this.signed = true; + return (T)this; + } + + public T encrypt(PublicKey publicKey) { + encrypt = true; + encryptionPublicKey = publicKey; + return (T)this; + } + + public T encryptionAlgorithm(String alg) { + this.encryptionAlgorithm = alg; + return (T)this; + } + + public T encryptionKeySize(int size) { + this.encryptionKeySize = size; + return (T)this; + } + public T signatureDigestMethod(String method) { this.signatureDigestMethod = method; return (T)this; @@ -61,13 +96,6 @@ public class SAML2PostBindingBuilder { return (T)this; } - public T sign(PrivateKey privateKey, PublicKey publicKey, X509Certificate cert) { - this.signingKeyPair = new KeyPair(publicKey, privateKey); - this.signingCertificate = cert; - this.signed = true; - return (T)this; - } - public T destination(String destination) { this.destination = destination; return (T)this; @@ -83,7 +111,48 @@ public class SAML2PostBindingBuilder { return (T)this; } + private String getSAMLNSPrefix(Document samlResponseDocument) { + Node assertionElement = samlResponseDocument.getDocumentElement() + .getElementsByTagNameNS(JBossSAMLURIConstants.ASSERTION_NSURI.get(), JBossSAMLConstants.ASSERTION.get()).item(0); + if (assertionElement == null) { + throw new IllegalStateException("Unable to find assertion in saml response document"); + } + + return assertionElement.getPrefix(); + } + + protected void encryptDocument(Document samlDocument) throws ProcessingException { + String samlNSPrefix = getSAMLNSPrefix(samlDocument); + + try { + QName encryptedAssertionElementQName = new QName(JBossSAMLURIConstants.ASSERTION_NSURI.get(), + JBossSAMLConstants.ENCRYPTED_ASSERTION.get(), samlNSPrefix); + + byte[] secret = WSTrustUtil.createRandomSecret(128 / 8); + SecretKey secretKey = new SecretKeySpec(secret, encryptionAlgorithm); + + // encrypt the Assertion element and replace it with a EncryptedAssertion element. + XMLEncryptionUtil.encryptElement(new QName(JBossSAMLURIConstants.ASSERTION_NSURI.get(), + JBossSAMLConstants.ASSERTION.get(), samlNSPrefix), samlDocument, encryptionPublicKey, + secretKey, encryptionKeySize, encryptedAssertionElementQName, true); + } catch (Exception e) { + throw new ProcessingException("failed to encrypt", e); + } + + } + + protected void encryptAndSign(Document samlDocument) throws ProcessingException { + if (encrypt) { + encryptDocument(samlDocument); + signDocument(samlDocument); + return; + } + if (signed) { + signDocument(samlDocument); + return; + } + } protected void signDocument(Document samlDocument) throws ProcessingException { SamlProtocolUtils.signDocument(samlDocument, signingKeyPair, signatureMethod, signatureDigestMethod, signingCertificate); diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2PostBindingErrorResponseBuilder.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2PostBindingErrorResponseBuilder.java index 6d56e51a78..727cd63c49 100755 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2PostBindingErrorResponseBuilder.java +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2PostBindingErrorResponseBuilder.java @@ -45,9 +45,7 @@ public class SAML2PostBindingErrorResponseBuilder extends SAML2PostBindingBuilde responseType.setStatus(JBossSAMLAuthnResponseFactory.createStatusTypeForResponder(status)); responseType.setDestination(destination); - if (signed) { - signDocument(samlResponse); - } + encryptAndSign(samlResponse); return samlResponse; } diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2PostBindingLogoutResponseBuilder.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2PostBindingLogoutResponseBuilder.java index 70c1909d80..d663237189 100755 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2PostBindingLogoutResponseBuilder.java +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2PostBindingLogoutResponseBuilder.java @@ -40,9 +40,7 @@ public class SAML2PostBindingLogoutResponseBuilder extends SAML2PostBindingBuild public String buildRequestString() { try { Document logoutRequestDocument = new SAML2Request().convert(createLogoutRequest()); - if (signed) { - signDocument(logoutRequestDocument); - } + encryptAndSign(logoutRequestDocument); byte[] responseBytes = DocumentUtil.getDocumentAsString(logoutRequestDocument).getBytes("UTF-8"); return PostBindingUtil.base64Encode(new String(responseBytes)); } catch (Exception e) { diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SalmProtocol.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SalmProtocol.java index c685146790..8a1222f766 100755 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SalmProtocol.java +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SalmProtocol.java @@ -18,6 +18,7 @@ import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.managers.ResourceAdminManager; import org.keycloak.services.resources.RealmsResource; import org.keycloak.services.resources.flows.Flows; +import org.keycloak.util.PemUtils; import org.picketlink.common.constants.GeneralConstants; import org.picketlink.common.constants.JBossSAMLURIConstants; import org.picketlink.identity.federation.core.saml.v2.constants.X500SAMLProfileConstants; @@ -26,6 +27,7 @@ import org.picketlink.identity.federation.web.handlers.saml2.SAML2LogOutHandler; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; +import java.security.PublicKey; /** * @author Bill Burke @@ -125,6 +127,16 @@ public class SalmProtocol implements LoginProtocol { if (requiresRealmSignature(client)) { builder.sign(realm.getPrivateKey(), realm.getPublicKey()); } + if (requiresEncryption(client)) { + PublicKey publicKey = null; + try { + publicKey = PemUtils.decodePublicKey(client.getAttribute(ClientModel.PUBLIC_KEY)); + } catch (Exception e) { + logger.error("failed", e); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response"); + } + builder.encrypt(publicKey); + } try { return builder.buildLoginResponse(); } catch (Exception e) { @@ -137,6 +149,10 @@ public class SalmProtocol implements LoginProtocol { return "true".equals(client.getAttribute("samlServerSignature")); } + private boolean requiresEncryption(ClientModel client) { + return "true".equals(client.getAttribute("samlEncrypt")); + } + public void initClaims(SALM2PostBindingLoginResponseBuilder builder, ClientModel model, UserModel user) { if (ClaimMask.hasEmail(model.getAllowedClaimsMask())) { builder.attribute(X500SAMLProfileConstants.EMAIL_ADDRESS.getFriendlyName(), user.getEmail()); @@ -166,6 +182,19 @@ public class SalmProtocol implements LoginProtocol { if (requiresRealmSignature(client)) { logoutBuilder.sign(realm.getPrivateKey(), realm.getPublicKey()); } + /* + if (requiresEncryption(client)) { + PublicKey publicKey = null; + try { + publicKey = PemUtils.decodePublicKey(client.getAttribute(ClientModel.PUBLIC_KEY)); + } catch (Exception e) { + logger.error("failed", e); + return; + } + logoutBuilder.encrypt(publicKey); + } + */ + String logoutRequestString = null; try { logoutRequestString = logoutBuilder.buildRequestString(); diff --git a/testsuite/integration/src/test/resources/testsaml.json b/testsuite/integration/src/test/resources/testsaml.json index 0cd30ba437..0dfc6e1758 100755 --- a/testsuite/integration/src/test/resources/testsaml.json +++ b/testsuite/integration/src/test/resources/testsaml.json @@ -56,6 +56,25 @@ "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVG8a7xGN6ZIkDbeecySygcDfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb40tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SVEr55KJoyQJQIDAQAB", "X509Certificate": "MIIB1DCCAT0CBgFJGP5dZDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1zaWcvMB4XDTE0MTAxNjEyNDQyM1oXDTI0MTAxNjEyNDYwM1owMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3Qtc2lnLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1RvGu8RjemSJA23nnMksoHA37MqY1DDTxOECY4rPAd9egr7GUNIXE0y1MokaR5R2crNpN8RIRwR8phQtQDjXL82c6W+NLQISxztarQJ7rdNJIYwHY0d5ri1XRpDP8zAuxubPYiMAVYcDkIcvlbBpwh/dRM5I2eElRK+eSiaMkCUCAwEAATANBgkqhkiG9w0BAQsFAAOBgQCLms6htnPaY69k1ntm9a5jgwSn/K61cdai8R8B0ccY7zvinn9AfRD7fiROQpFyY29wKn8WCLrJ86NBXfgFUGyR5nLNHVy3FghE36N2oHy53uichieMxffE6vhkKJ4P8ChfJMMOZlmCPsQPDvjoAghHt4mriFiQgRdPgIy/zDjSNw==" } + }, + { + "name": "http://localhost:8080/sales-post-enc/", + "enabled": true, + "protocol": "saml", + "fullScopeAllowed": true, + "baseUrl": "http://localhost:8080/sales-post-enc", + "adminUrl": "http://localhost:8080/sales-post-enc", + "redirectUris": [ + "http://localhost:8080/sales-post-enc/*" + ], + "attributes": { + "samlServerSignature": "true", + "samlClientSignature": "true", + "samlEncrypt": "true", + "privateKey": "MIICXQIBAAKBgQDb7kwJPkGdU34hicplwfp6/WmNcaLh94TSc7Jyr9Undp5pkyLgb0DE7EIE+6kSs4LsqCb8HDkB0nLD5DXbBJFd8n0WGoKstelvtg6FtVJMnwN7k7yZbfkPECWH9zF70VeOo9vbzrApNRnct8ZhH5fbflRB4JMA9L9R+LbURdoSKQIDAQABAoGBANtbZG9bruoSGp2s5zhzLzd4hczT6Jfk3o9hYjzNb5Z60ymN3Z1omXtQAdEiiNHkRdNxK+EM7TcKBfmoJqcaeTkW8cksVEAW23ip8W9/XsLqmbU2mRrJiKa+KQNDSHqJi1VGyimi4DDApcaqRZcaKDFXg2KDr/Qt5JFD/o9IIIPZAkEA+ZENdBIlpbUfkJh6Ln+bUTss/FZ1FsrcPZWu13rChRMrsmXsfzu9kZUWdUeQ2Dj5AoW2Q7L/cqdGXS7Mm5XhcwJBAOGZq9axJY5YhKrsksvYRLhQbStmGu5LG75suF+rc/44sFq+aQM7+oeRr4VY88Mvz7mk4esdfnk7ae+cCazqJvMCQQCx1L1cZw3yfRSn6S6u8XjQMjWE/WpjulujeoRiwPPY9WcesOgLZZtYIH8nRL6ehEJTnMnahbLmlPFbttxPRUanAkA11MtSIVcKzkhp2KV2ipZrPJWwI18NuVJXb+3WtjypTrGWFZVNNkSjkLnHIeCYlJIGhDd8OL9zAiBXEm6kmgLNAkBWAg0tK2hCjvzsaA505gWQb4X56uKWdb0IzN+fOLB3Qt7+fLqbVQNQoNGzqey6B4MoS1fUKAStqdGTFYPG/+9t", + "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDb7kwJPkGdU34hicplwfp6/WmNcaLh94TSc7Jyr9Undp5pkyLgb0DE7EIE+6kSs4LsqCb8HDkB0nLD5DXbBJFd8n0WGoKstelvtg6FtVJMnwN7k7yZbfkPECWH9zF70VeOo9vbzrApNRnct8ZhH5fbflRB4JMA9L9R+LbURdoSKQIDAQAB", + "X509Certificate": "MIIB1DCCAT0CBgFJGVacCDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1lbmMvMB4XDTE0MTAxNjE0MjA0NloXDTI0MTAxNjE0MjIyNlowMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3QtZW5jLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA2+5MCT5BnVN+IYnKZcH6ev1pjXGi4feE0nOycq/VJ3aeaZMi4G9AxOxCBPupErOC7Kgm/Bw5AdJyw+Q12wSRXfJ9FhqCrLXpb7YOhbVSTJ8De5O8mW35DxAlh/cxe9FXjqPb286wKTUZ3LfGYR+X235UQeCTAPS/Ufi21EXaEikCAwEAATANBgkqhkiG9w0BAQsFAAOBgQBMrfGD9QFfx5v7ld/OAto5rjkTe3R1Qei8XRXfcs83vLaqEzjEtTuLGrJEi55kXuJgBpVmQpnwCCkkjSy0JxbqLDdVi9arfWUxEGmOr01ZHycELhDNaQcFqVMPr5kRHIHgktT8hK2IgCvd3Fy9/JCgUgCPxKfhwecyEOKxUc857g==" + } } ], "roles" : {