saml encryption

This commit is contained in:
Bill Burke 2014-10-16 11:44:51 -04:00
parent 638ce06f77
commit 3e5afcde9e
8 changed files with 140 additions and 28 deletions

View file

@ -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";
}

View file

@ -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>

View file

@ -174,9 +174,8 @@ public class SALM2PostBindingLoginResponseBuilder extends SAML2PostBindingBuilde
throw logger.samlAssertionMarshallError(e);
}
if (signed) {
signDocument(samlResponseDocument);
}
encryptAndSign(samlResponseDocument);
return samlResponseDocument;
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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) {

View file

@ -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();

View file

@ -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" : {