keycloak-aplcache
Changes
forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js 14(+7 -7)
forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-detail.html 10(+5 -5)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SALM2PostBindingLoginResponseBuilder.java 5(+2 -3)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2PostBindingErrorResponseBuilder.java 4(+1 -3)
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" : {