keycloak-aplcache

Details

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 03dce99..6d68bac 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 00eec89..d137708 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 @@
                     <span tooltip-placement="right" tooltip="'Confidential' applications require a secret to initiate login protocol.  'Public' clients do not require a secret.  'Bearer-only' applications are web services that never initiate a login." class="fa fa-info-circle"></span>
                 </div>
                 <div class="form-group clearfix block" data-ng-show="protocol == 'saml'">
-                    <label class="col-sm-2 control-label" for="samlServerSignature">Server Signatures</label>
+                    <label class="col-sm-2 control-label" for="samlServerSignature">Sign SAML Documents</label>
                     <div class="col-sm-6">
                         <input ng-model="samlServerSignature" ng-click="switchChange()" name="samlServerSignature" id="samlServerSignature" onoffswitch />
                     </div>
-                    <span tooltip-placement="right" tooltip="Should server sent SAML requests and responses be signed by the realm?" class="fa fa-info-circle"></span>
+                    <span tooltip-placement="right" tooltip="Should SAML documents be signed by the realm?" class="fa fa-info-circle"></span>
                 </div>
                 <div class="form-group clearfix block" data-ng-show="protocol == 'saml'">
-                    <label class="col-sm-2 control-label" for="samlServerEncrypt">Server Encryption</label>
+                    <label class="col-sm-2 control-label" for="samlEncrypt">Encrypt SAML Documents</label>
                     <div class="col-sm-6">
-                        <input ng-model="samlServerEncrypt" ng-click="switchChange()" name="samlServerEncrypt" id="samlServerEncrypt" onoffswitch />
+                        <input ng-model="samlEncrypt" ng-click="switchChange()" name="samlEncrypt" id="samlEncrypt" onoffswitch />
                     </div>
-                    <span tooltip-placement="right" tooltip="Should server sent SAML requests and responses be encrypted by the realm?" class="fa fa-info-circle"></span>
+                    <span tooltip-placement="right" tooltip="Should SAML asserts be encrypted with client's public key?" class="fa fa-info-circle"></span>
                 </div>
                 <div class="form-group clearfix block" data-ng-show="protocol == 'saml'">
                     <label class="col-sm-2 control-label" for="samlClientSignature">Client Signatures</label>
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 c65cbf3..e067204 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/SalmProtocol.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SalmProtocol.java
index c685146..8a1222f 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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -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/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 a1b105c..07cd939 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<T extends 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<T extends 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<T extends 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<T extends 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 6d56e51..727cd63 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 70c1909..d663237 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/testsuite/integration/src/test/resources/testsaml.json b/testsuite/integration/src/test/resources/testsaml.json
index 0cd30ba..0dfc6e1 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" : {