LogoutUtil.java revision 0cd8368ca65c58915ee90bc73d84e65f3da9e120
/**
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2006 Sun Microsystems Inc. All Rights Reserved
*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
* https://opensso.dev.java.net/public/CDDLv1.0.html or
* opensso/legal/CDDLv1.0.txt
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at opensso/legal/CDDLv1.0.txt.
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* $Id: LogoutUtil.java,v 1.16 2009/11/20 21:41:16 exu Exp $
*
*/
/*
* Portions Copyrighted 2012-2013 ForgeRock, Inc.
*/
package com.sun.identity.saml2.profile;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import com.sun.identity.shared.encode.Base64;
import com.sun.identity.shared.encode.URLEncDec;
import com.sun.identity.shared.debug.Debug;
import com.sun.identity.shared.xml.XMLUtils;
import com.sun.identity.saml.common.SAMLUtils;
import com.sun.identity.saml.xmlsig.KeyProvider;
import com.sun.identity.saml2.assertion.EncryptedID;
import com.sun.identity.saml2.assertion.Issuer;
import com.sun.identity.saml2.assertion.NameID;
import com.sun.identity.saml2.common.SAML2Constants;
import com.sun.identity.saml2.common.SAML2Exception;
import com.sun.identity.saml2.common.SAML2Utils;
import com.sun.identity.saml2.jaxb.entityconfig.BaseConfigType;
import com.sun.identity.saml2.jaxb.metadata.IDPSSODescriptorElement;
import com.sun.identity.saml2.jaxb.metadata.KeyDescriptorType;
import com.sun.identity.saml2.jaxb.metadata.SPSSODescriptorElement;
import com.sun.identity.saml2.jaxb.metadata.SingleLogoutServiceElement;
import com.sun.identity.saml2.key.EncInfo;
import com.sun.identity.saml2.key.KeyUtil;
import com.sun.identity.saml2.logging.LogUtil;
import com.sun.identity.saml2.meta.SAML2MetaException;
import com.sun.identity.saml2.meta.SAML2MetaManager;
import com.sun.identity.saml2.meta.SAML2MetaUtils;
import com.sun.identity.saml2.plugins.FedletAdapter;
import com.sun.identity.saml2.protocol.Extensions;
import com.sun.identity.saml2.protocol.LogoutRequest;
import com.sun.identity.saml2.protocol.LogoutResponse;
import com.sun.identity.saml2.protocol.ProtocolFactory;
import com.sun.identity.saml2.protocol.SessionIndex;
import com.sun.identity.saml2.protocol.Status;
import com.sun.identity.saml2.protocol.StatusDetail;
import com.sun.identity.plugin.session.SessionException;
/**
* This class constructs the <code>LogoutRequest</code> and executes
* the required processing logic for sending <code>LogoutRequest</code>
* from SP to IDP.
*/
public class LogoutUtil {
static KeyProvider keyProvider = KeyUtil.getKeyProviderInstance();
static SAML2MetaManager metaManager = null;
static Debug debug = SAML2Utils.debug;
static {
metaManager= SAML2Utils.getSAML2MetaManager();
}
/**
* Builds the <code>LogoutRequest</code> and executes
* the required processing logic for sending <code>LogoutRequest</code>
* from SP to IDP.
*
* @param metaAlias the requester's metaAlais.
* @param recipientEntityID the recipient's entity ID.
* @param recipientSLOList recipient's Single Logout Service location
* URL list.
* @param extensionsList Extension list for request.
* @param binding binding used for this request.
* @param relayState the target URL on successful Single Logout.
* @param sessionIndex sessionIndex of the Assertion generated by the
* Identity Provider or Service Provider.
* @param nameID <code>NameID</code> of the Provider.
* @param response the HttpServletResponse.
* @param paramsMap Map of all other parameters.
* Following parameters names with their respective
* String values are allowed in this paramsMap.
* "realm" - MetaAlias for Service Provider. The format of
* this parameter is /realm_name/SP name.
* "RelayState" - the target URL on successful Single Logout
* "Destination" - A URI Reference indicating the address to
* which the request has been sent.
* "Consent" - Specifies a URI a SAML defined identifier
* known as Consent Identifiers.
* @param config entity base config for basic auth.
*
* @return Logout request ID
*
* @throws SAML2Exception if error initiating request to IDP.
* @throws SessionException if error initiating request to IDP.
*/
public static StringBuffer doLogout(
String metaAlias,
String recipientEntityID,
List recipientSLOList,
List extensionsList,
String binding,
String relayState,
String sessionIndex,
NameID nameID,
HttpServletRequest request,
HttpServletResponse response,
Map paramsMap,
BaseConfigType config) throws SAML2Exception, SessionException {
StringBuffer logoutRequestID = new StringBuffer();
String classMethod = "LogoutUtil.doLogout: ";
String requesterEntityID = metaManager.getEntityByMetaAlias(metaAlias);
String realm = SAML2MetaUtils.getRealmByMetaAlias(metaAlias);
String hostEntityRole = SAML2Utils.getHostEntityRole(paramsMap);
if (debug.messageEnabled()) {
debug.message(
classMethod + "Entering ..." +
"\nrequesterEntityID=" + requesterEntityID +
"\nrecipientEntityID=" + recipientEntityID +
"\nbinding=" + binding +
"\nrelayState=" + relayState +
"\nsessionIndex=" + sessionIndex);
}
// generate unique request ID
String requestID = SAML2Utils.generateID();
if ((requestID == null) || (requestID.length() == 0)) {
throw new SAML2Exception(
SAML2Utils.bundle.getString("cannotGenerateID"));
}
// retrieve data from the params map
// destinationURI required if message is signed.
String destinationURI = SAML2Utils.getParameter(paramsMap,
SAML2Constants.DESTINATION);
String consent = SAML2Utils.getParameter(
paramsMap,SAML2Constants.CONSENT);
Extensions extensions = createExtensions(extensionsList);
Issuer issuer = SAML2Utils.createIssuer(requesterEntityID);
// construct LogoutRequest
LogoutRequest logoutReq = null;
try {
logoutReq = ProtocolFactory.getInstance().createLogoutRequest();
} catch (Exception e) {
debug.error(
classMethod +
"Unable to create LogoutRequest : ", e);
throw new SAML2Exception(
SAML2Utils.bundle.getString("errorCreatingLogoutRequest"));
}
// set required attributes / elements
logoutReq.setID(requestID);
logoutReq.setVersion(SAML2Constants.VERSION_2_0);
logoutReq.setIssueInstant(new Date());
setNameIDForSLORequest(logoutReq, nameID, realm, requesterEntityID,
hostEntityRole, recipientEntityID);
// set optional attributes / elements
logoutReq.setDestination(XMLUtils.escapeSpecialCharacters(
destinationURI));
logoutReq.setConsent(consent);
logoutReq.setIssuer(issuer);
if (hostEntityRole.equals(SAML2Constants.IDP_ROLE)) {
// use the assertion effective time (in seconds)
int effectiveTime = SAML2Constants.ASSERTION_EFFECTIVE_TIME;
String effectiveTimeStr = SAML2Utils.getAttributeValueFromSSOConfig(
realm, requesterEntityID, SAML2Constants.IDP_ROLE,
SAML2Constants.ASSERTION_EFFECTIVE_TIME_ATTRIBUTE);
if (effectiveTimeStr != null) {
try {
effectiveTime = Integer.parseInt(effectiveTimeStr);
if (SAML2Utils.debug.messageEnabled()) {
SAML2Utils.debug.message(classMethod +
"got effective time from config:" + effectiveTime);
}
} catch (NumberFormatException nfe) {
SAML2Utils.debug.error(classMethod +
"Failed to get assertion effective time from " +
"IDP SSO config: ", nfe);
effectiveTime = SAML2Constants.ASSERTION_EFFECTIVE_TIME;
}
}
Date date = new Date();
date.setTime(date.getTime() + effectiveTime * 1000);
logoutReq.setNotOnOrAfter(date);
}
if (extensions != null) {
logoutReq.setExtensions(extensions);
}
if (sessionIndex != null) {
List list = new ArrayList();
list.add(sessionIndex);
logoutReq.setSessionIndex(list);
}
// get recipient's logout service location
// based on binding
String location = getSLOServiceLocation(recipientSLOList,binding);
if (location == null || location.length() == 0) {
debug.error(classMethod +
"Unable to find the recipient's single logout " +
"service with the binding " + binding);
throw new SAML2Exception(
SAML2Utils.bundle.getString("sloServiceNotfound"));
} else {
debug.message(classMethod +
"Recipient's single logout service location = " +
location);
if (destinationURI == null || (destinationURI.length() == 0)) {
logoutReq.setDestination(XMLUtils.escapeSpecialCharacters(
location));
}
}
if (debug.messageEnabled()) {
debug.message(classMethod + "SLO Request before signing : ");
debug.message(logoutReq.toXMLString(true,true));
}
if (binding.equals(SAML2Constants.HTTP_REDIRECT)) {
try {
doSLOByHttpRedirect(logoutReq.toXMLString(true, true),
location,
relayState,
realm,
requesterEntityID,
hostEntityRole,
recipientEntityID,
response);
logoutRequestID.append(requestID);
String[] data = {location};
LogUtil.access(Level.INFO,LogUtil.REDIRECT_TO_IDP, data,
null);
} catch (Exception e) {
debug.error("Exception :",e);
throw new SAML2Exception(
SAML2Utils.bundle.getString(
"errorRedirectingLogoutRequest"));
}
} else if (binding.equals(SAML2Constants.SOAP)) {
logoutRequestID.append(requestID);
signSLORequest(logoutReq, realm, requesterEntityID,
hostEntityRole, recipientEntityID);
if (debug.messageEnabled()) {
debug.message(classMethod + "SLO Request after signing : ");
debug.message(logoutReq.toXMLString(true,true));
}
location = SAML2Utils.fillInBasicAuthInfo(
config, location);
doSLOBySOAP(requestID, logoutReq,
location, realm, requesterEntityID, hostEntityRole,
request, response);
} else if (binding.equals(SAML2Constants.HTTP_POST)) {
logoutRequestID.append(requestID);
signSLORequest(logoutReq, realm, requesterEntityID,
hostEntityRole, recipientEntityID);
if (debug.messageEnabled()) {
debug.message(classMethod + "SLO Request after signing : ");
debug.message(logoutReq.toXMLString(true,true));
}
doSLOByPOST(requestID, logoutReq.toXMLString(true,true),
location, relayState,realm, requesterEntityID, hostEntityRole,
response);
}
SPCache.logoutRequestIDHash.put(logoutRequestID.toString(), logoutReq);
return logoutRequestID;
}
static private void doSLOByHttpRedirect(
String sloRequestXMLString,
String sloURL,
String relayState,
String realm,
String hostEntity,
String hostEntityRole,
String remoteEntity,
HttpServletResponse response) throws SAML2Exception, IOException {
String method = "doSLOByHttpRedirect: ";
// encode the xml string
String encodedXML = SAML2Utils.encodeForRedirect(sloRequestXMLString);
StringBuffer queryString =
new StringBuffer().append(SAML2Constants.SAML_REQUEST)
.append(SAML2Constants.EQUAL)
.append(encodedXML);
// spec states that the relay state MUST NOT exceed 80
// chanracters, need to have some means to pass it when it
// exceeds 80 chars
if ((relayState != null) && (relayState.length() > 0)) {
String tmp = SAML2Utils.generateID();
if (hostEntityRole.equals(SAML2Constants.IDP_ROLE)) {
IDPCache.relayStateCache.put(tmp, relayState);
} else {
SPCache.relayStateHash.put(tmp, new CacheObject(relayState));
}
queryString.append("&").append(SAML2Constants.RELAY_STATE)
.append("=").append(tmp);
}
boolean needToSign = false;
if (hostEntityRole.equalsIgnoreCase(SAML2Constants.IDP_ROLE)) {
needToSign =
SAML2Utils.getWantLogoutRequestSigned(realm, remoteEntity,
SAML2Constants.SP_ROLE);
} else {
needToSign =
SAML2Utils.getWantLogoutRequestSigned(realm, remoteEntity,
SAML2Constants.IDP_ROLE);
}
String signedQueryString = queryString.toString();
if (needToSign == true) {
signedQueryString = SAML2Utils.signQueryString(signedQueryString,
realm, hostEntity, hostEntityRole);
}
String redirectURL = sloURL + (sloURL.contains("?") ? "&" : "?") +
signedQueryString;
if (debug.messageEnabled()) {
debug.message(method + "LogoutRequestXMLString : "
+ sloRequestXMLString);
debug.message(method + "LogoutRedirectURL : " + sloURL);
}
response.sendRedirect(redirectURL);
}
/**
* Performs SOAP logout, this method will send LogoutResuest to IDP using
* SOAP binding, and process LogoutResponse.
* @param requestID Request id.
* @param sloRequest a string representation of LogoutRequest.
* @param sloURL SOAP logout URL on IDP side.
* @param realm a string representation of LogoutRequest.
* @param hostEntity host entity is sending the request.
* @param hostRole SOAP logout URL on IDP side.
* @throws SAML2Exception if logout failed.
* @throws SessionException if logout failed.
*/
private static void doSLOBySOAP(
String requestID,
LogoutRequest sloRequest,
String sloURL,
String realm,
String hostEntity,
String hostRole,
HttpServletRequest request,
HttpServletResponse response) throws SAML2Exception, SessionException {
String sloRequestXMLString = sloRequest.toXMLString(true, true);
if (debug.messageEnabled()) {
debug.message("LogoutUtil.doSLOBySOAP : SLORequestXML: "
+ sloRequestXMLString + "\nSOAPURL : " + sloURL);
}
SOAPMessage resMsg = null;
try {
resMsg = SAML2Utils.sendSOAPMessage(sloRequestXMLString, sloURL,
true);
} catch (SOAPException se) {
debug.error("Unable to send SOAPMessage to IDP ", se);
throw new SAML2Exception(se.getMessage());
}
// get the LogoutResponse element from SOAP message
Element respElem = SAML2Utils.getSamlpElement(resMsg, "LogoutResponse");
LogoutResponse sloResponse = ProtocolFactory.getInstance()
.createLogoutResponse(respElem);
String userId = null;
// invoke SPAdapter for preSingleLogoutProcess : SP initiated SOAP
if ((hostRole != null) && hostRole.equals(SAML2Constants.SP_ROLE)) {
userId = SPSingleLogout.preSingleLogoutProcess(hostEntity, realm,
request, response, null, sloRequest, sloResponse,
SAML2Constants.SOAP);
}
if (sloResponse == null) {
debug.error("LogoutUtil.doSLOBySoap : null response");
throw new SAML2Exception(SAML2Utils.bundle.getString(
"nullLogoutResponse"));
}
if (debug.messageEnabled()) {
debug.message("LogoutUtil.doSLOBySOAP : " +
"LogoutResponse without SOAP envelope:\n" +
sloResponse.toXMLString());
}
Issuer resIssuer = sloResponse.getIssuer();
String requestId = sloResponse.getInResponseTo();
SAML2Utils.verifyResponseIssuer(
realm, hostEntity, resIssuer, requestId);
String remoteEntityID = sloResponse.getIssuer().getValue();
verifySLOResponse(sloResponse, realm, remoteEntityID, hostEntity,
hostRole);
boolean success = checkSLOResponse(sloResponse, requestID);
if (debug.messageEnabled()) {
debug.message("Request success : " + success);
}
if (success == false) {
if (SPCache.isFedlet) {
FedletAdapter fedletAdapter =
SAML2Utils.getFedletAdapterClass(hostEntity, realm);
if (fedletAdapter != null) {
fedletAdapter.onFedletSLOFailure(
request, response, sloRequest, sloResponse,
hostEntity, remoteEntityID, SAML2Constants.SOAP);
}
}
throw new SAML2Exception(SAML2Utils.bundle.getString("sloFailed"));
} else {
// invoke SPAdapter for postSLOSuccess : SP inited SOAP
if ((hostRole != null) && hostRole.equals(SAML2Constants.SP_ROLE)) {
if (SPCache.isFedlet) {
FedletAdapter fedletAdapter =
SAML2Utils.getFedletAdapterClass(hostEntity, realm);
if (fedletAdapter != null) {
fedletAdapter.onFedletSLOSuccess(
request, response, sloRequest, sloResponse,
hostEntity, remoteEntityID, SAML2Constants.SOAP);
}
} else {
SPSingleLogout.postSingleLogoutSuccess(hostEntity, realm,
request, response, userId, sloRequest, sloResponse,
SAML2Constants.SOAP);
}
}
}
}
private static boolean checkSLOResponse(LogoutResponse sloResponse,
String requestID)
throws SAML2Exception {
boolean success = false;
String retCode = sloResponse.getStatus().getStatusCode().getValue();
if (retCode.equalsIgnoreCase(SAML2Constants.SUCCESS)) {
String inResponseTo = sloResponse.getInResponseTo();
if (inResponseTo.equals(requestID)) {
if (debug.messageEnabled()) {
debug.message("LogoutUtil.doSLOBySOAP " +
"LogoutResponse inResponseTo matches LogoutRequest ID");
}
} else {
debug.error("LogoutUtil.doSLOBySOAP " +
"LogoutResponse inResponseTo does not match Request ID.");
throw new SAML2Exception(SAML2Utils.bundle.getString(
"inResponseToNoMatch"));
}
success = true;
} else {
if (debug.messageEnabled()) {
debug.message("LogoutUtil.doSLOBySOAP : " +
"return code : " + retCode);
}
success = false;
}
return success;
}
static LogoutResponse forwardToRemoteServer(LogoutRequest logoutReq,
String remoteLogoutURL) {
if (debug.messageEnabled()) {
debug.message("LogoutUtil.forwardToRemoteServer: " +
"remoteLogoutURL = " + remoteLogoutURL);
}
try {
SOAPMessage resMsg = SAML2Utils.sendSOAPMessage(
logoutReq.toXMLString(true,true), remoteLogoutURL, true);
// get the LogoutResponse element from SOAP message
Element respElem = SAML2Utils.getSamlpElement(resMsg,
"LogoutResponse");
return ProtocolFactory.getInstance().createLogoutResponse(respElem);
} catch (Exception ex) {
if (debug.messageEnabled()) {
debug.message("LogoutUtil.forwardToRemoteServer:", ex);
}
}
return null;
}
static List getSessionIndex(LogoutResponse logoutRes) {
StatusDetail statusDetail = logoutRes.getStatus().getStatusDetail();
if (statusDetail == null) {
return null;
}
List details = statusDetail.getAny();
if (details == null || details.isEmpty()) {
return null;
}
List sessionIndexList = new ArrayList();
for(Iterator iter = details.iterator(); iter.hasNext();) {
String detail = (String)iter.next();
Document doc = XMLUtils.toDOMDocument(detail, debug);
Element elem = doc.getDocumentElement();
String localName = elem.getLocalName();
if (SAML2Constants.SESSION_INDEX.equals(localName)) {
sessionIndexList.add(XMLUtils.getElementString(elem));
}
}
return sessionIndexList;
}
static void setSessionIndex(Status status, List sessionIndex) {
try {
StatusDetail sd=ProtocolFactory.getInstance().createStatusDetail();
status.setStatusDetail(sd);
if (sessionIndex != null && !sessionIndex.isEmpty()) {
List details = new ArrayList();
for(Iterator iter = sessionIndex.iterator(); iter.hasNext();) {
String si = (String)iter.next();
SessionIndex sIndex = ProtocolFactory.getInstance()
.createSessionIndex(si);
details.add(sIndex.toXMLString(true, true));
}
sd.setAny(details);
}
} catch (SAML2Exception e) {
debug.error("LogoutUtil.setSessionIndex: ", e);
}
}
/**
* Gets Single Logout Service location URL.
*
* @param sloList list of configured <code>SingleLogoutElement</code>.
* @param desiredBinding desired binding of SingleLogout.
* @return url of desiredBinding.
*/
public static String getSLOServiceLocation(
List sloList,
String desiredBinding) {
String classMethod =
"LogoutUtil.getSLOserviceLocation: ";
int n = sloList.size();
if (debug.messageEnabled()) {
debug.message(
classMethod +
"Number of single logout services = " +
n);
}
SingleLogoutServiceElement slos = null;
String location = null;
String binding = null;
for (int i=0; i<n; i++) {
slos = (SingleLogoutServiceElement)sloList.get(i);
if (slos != null) {
binding = slos.getBinding();
}
if (debug.messageEnabled()) {
debug.message(
classMethod +
"Single logout service binding = " +
binding);
}
if ((binding != null) && (binding.equals(desiredBinding))) {
location = slos.getLocation();
if (debug.messageEnabled()) {
debug.message(
classMethod +
"Found the single logout service "+
"with the desired binding");
}
break;
}
}
return location;
}
/**
* Gets Single Logout Response Service location URL.
*
* @param sloList list of configured <code>SingleLogoutElement</code>.
* @param desiredBinding desired binding of SingleLogout.
* @return url of desiredBinding.
*/
public static String getSLOResponseServiceLocation(
List sloList,
String desiredBinding) {
String classMethod =
"LogoutUtil.getSLOResponseServiceLocation: ";
int n = sloList.size();
if (debug.messageEnabled()) {
debug.message(
classMethod +
"Number of single logout services = " +
n);
}
SingleLogoutServiceElement slos = null;
String resLocation = null;
String binding = null;
for (int i=0; i<n; i++) {
slos = (SingleLogoutServiceElement)sloList.get(i);
if (slos != null) {
binding = slos.getBinding();
}
if (debug.messageEnabled()) {
debug.message(
classMethod +
"Single logout service binding = " +
binding);
}
if ((binding != null) && (binding.equals(desiredBinding))) {
resLocation = slos.getResponseLocation();
if (debug.messageEnabled()) {
debug.message(
classMethod +
"Found the single logout service "+
"with the desired binding");
}
break;
}
}
return resLocation;
}
/* Creates Extensions */
private static com.sun.identity.saml2.protocol.Extensions
createExtensions(List extensionsList) throws SAML2Exception {
Extensions extensions = null;
if (extensionsList != null && !extensionsList.isEmpty()) {
extensions = ProtocolFactory.getInstance().createExtensions();
extensions.setAny(extensionsList);
}
return extensions;
}
/**
* Builds the <code>LogoutResponse</code> to be sent to IDP.
*
* @param status status of the response.
* @param inResponseTo inResponseTo.
* @param issuer issuer of the response, which is SP.
* @param realm inResponseTo.
* @param hostRole issuer of the response, which is SP.
* @param remoteEntity will get this response.
*
* @return <code>LogoutResponse</code>
*
*/
public static LogoutResponse generateResponse(
Status status,
String inResponseTo,
Issuer issuer,
String realm,
String hostRole,
String remoteEntity) {
if (status == null) {
status = SAML2Utils.generateStatus(SAML2Constants.SUCCESS,
SAML2Utils.bundle.getString("requestSuccess"));
}
LogoutResponse logoutResponse = ProtocolFactory.getInstance()
.createLogoutResponse();
String responseID = SAMLUtils.generateID();
try {
logoutResponse.setStatus(status);
logoutResponse.setID(responseID);
logoutResponse.setInResponseTo(inResponseTo);
logoutResponse.setVersion(SAML2Constants.VERSION_2_0);
logoutResponse.setIssueInstant(new Date());
logoutResponse.setIssuer(issuer);
} catch (SAML2Exception e) {
debug.error("Error in generating LogoutResponse.", e);
}
return logoutResponse;
}
/**
* Sign LogoutRequest.
*
* @param sloRequest SLO request will be signed.
* @param realm realm of host entity.
* @param hostEntity entity ID of host entity.
* @param hostEntityRole role of host entity.
* @param remoteEntity entity ID of remote host entity.
* @throws SAML2Exception if error in signing the request.
*/
public static void signSLORequest(LogoutRequest sloRequest,
String realm, String hostEntity,
String hostEntityRole, String remoteEntity)
throws SAML2Exception {
signSLORequest(sloRequest, realm, hostEntity,
hostEntityRole, remoteEntity, false);
}
static void signSLORequest(LogoutRequest sloRequest,
String realm, String hostEntity,
String hostEntityRole, String remoteEntity,
boolean includeCert)
throws SAML2Exception {
String method = "signSLORequest : ";
boolean needRequestSign = false;
if (hostEntityRole.equalsIgnoreCase(SAML2Constants.IDP_ROLE)) {
needRequestSign = SAML2Utils.getWantLogoutRequestSigned(realm,
remoteEntity, SAML2Constants.SP_ROLE);
} else {
needRequestSign = SAML2Utils.getWantLogoutRequestSigned(realm,
remoteEntity, SAML2Constants.IDP_ROLE);
}
if (needRequestSign == false) {
if (debug.messageEnabled()) {
debug.message(method + "SLORequest doesn't need to be signed.");
}
return;
}
String alias = SAML2Utils.getSigningCertAlias(realm, hostEntity, hostEntityRole);
String encryptedKeyPass = SAML2Utils.getSigningCertEncryptedKeyPass(realm, hostEntity, hostEntityRole);
if (debug.messageEnabled()) {
debug.message(method + "realm is : "+ realm);
debug.message(method + "hostEntity is : " + hostEntity);
debug.message(method + "Host Entity role is : " + hostEntityRole);
debug.message(method + "Cert Alias is : " + alias);
debug.message(method + "SLO Request before sign : "
+ sloRequest.toXMLString(true, true));
if (encryptedKeyPass != null && !encryptedKeyPass.isEmpty()) {
debug.message(method + "Using provided Cert KeyPass");
}
}
PrivateKey signingKey;
if (encryptedKeyPass == null || encryptedKeyPass.isEmpty()) {
signingKey = keyProvider.getPrivateKey(alias);
} else {
signingKey = keyProvider.getPrivateKey(alias, encryptedKeyPass);
}
X509Certificate signingCert = null;
if (includeCert) {
signingCert = keyProvider.getX509Certificate(alias);
}
if (signingKey != null) {
sloRequest.sign(signingKey, signingCert);
} else {
debug.error("Incorrect configuration for Signing Certificate.");
throw new SAML2Exception(
SAML2Utils.bundle.getString("metaDataError"));
}
if (debug.messageEnabled()) {
debug.message(method + "SLO Request after sign : "
+ sloRequest.toXMLString(true, true));
}
}
/**
* Verify the signature in LogoutRequest.
*
* @param sloRequest SLO request will be verified.
* @param realm realm of host entity.
* @param remoteEntity entity ID of remote host entity.
* @param hostEntity entity ID of host entity.
* @param hostEntityRole role of host entity.
* @return returns true if signature is valid.
* @throws SAML2Exception if error in verifying the signature.
* @throws SessionException if error in verifying the signature.
*/
public static boolean verifySLORequest(LogoutRequest sloRequest,
String realm, String remoteEntity,
String hostEntity, String hostEntityRole)
throws SAML2Exception, SessionException {
String method = "verifySLORequest : ";
boolean needVerifySignature =
SAML2Utils.getWantLogoutRequestSigned(realm, hostEntity,
hostEntityRole);
if (needVerifySignature == false) {
if (debug.messageEnabled()) {
debug.message(method+"SLORequest doesn't need to be verified.");
}
return true;
}
if (debug.messageEnabled()) {
debug.message(method + "realm is : "+ realm);
debug.message(method + "remoteEntity is : " + remoteEntity);
debug.message(method + "Host Entity role is : " + hostEntityRole);
}
boolean valid = false;
X509Certificate signingCert = null;
if (hostEntityRole.equalsIgnoreCase(SAML2Constants.IDP_ROLE)) {
SPSSODescriptorElement spSSODesc =
metaManager.getSPSSODescriptor(realm, remoteEntity);
signingCert =
KeyUtil.getVerificationCert(spSSODesc, remoteEntity,
SAML2Constants.SP_ROLE);
} else {
IDPSSODescriptorElement idpSSODesc =
metaManager.getIDPSSODescriptor(realm, remoteEntity);
signingCert =
KeyUtil.getVerificationCert(idpSSODesc, remoteEntity,
SAML2Constants.IDP_ROLE);
}
if (signingCert != null) {
valid = sloRequest.isSignatureValid(signingCert);
if (debug.messageEnabled()) {
debug.message(method + "Signature is : " + valid);
}
} else {
debug.error("Incorrect configuration for Signing Certificate.");
throw new SAML2Exception(
SAML2Utils.bundle.getString("metaDataError"));
}
return valid;
}
/**
* Sign LogoutResponse.
*
* @param sloResponse SLO response will be signed.
* @param realm realm of host entity.
* @param hostEntity entity ID of host entity.
* @param hostEntityRole role of host entity.
* @param remoteEntity entity ID of remote host entity.
* @throws SAML2Exception if error in signing the request.
*/
public static void signSLOResponse(LogoutResponse sloResponse,
String realm, String hostEntity,
String hostEntityRole, String remoteEntity)
throws SAML2Exception {
signSLOResponse(sloResponse, realm, hostEntity,
hostEntityRole, remoteEntity, false);
}
static void signSLOResponse(LogoutResponse sloResponse,
String realm, String hostEntity,
String hostEntityRole, String remoteEntity,
boolean includeCert)
throws SAML2Exception {
String method = "signSLOResponse : ";
boolean needSignResponse = false;
if (hostEntityRole.equalsIgnoreCase(SAML2Constants.IDP_ROLE)) {
needSignResponse = SAML2Utils.getWantLogoutResponseSigned(realm,
remoteEntity, SAML2Constants.SP_ROLE);
} else {
needSignResponse = SAML2Utils.getWantLogoutResponseSigned(realm,
remoteEntity, SAML2Constants.IDP_ROLE);
}
if (needSignResponse == false) {
if (debug.messageEnabled()) {
debug.message(method +
"SLOResponse doesn't need to be signed.");
}
return;
}
String alias = SAML2Utils.getSigningCertAlias(realm, hostEntity, hostEntityRole);
String encryptedKeyPass = SAML2Utils.getSigningCertEncryptedKeyPass(realm, hostEntity, hostEntityRole);
if (debug.messageEnabled()) {
debug.message(method + "realm is : "+ realm);
debug.message(method + "hostEntity is : " + hostEntity);
debug.message(method + "Host Entity role is : " + hostEntityRole);
debug.message(method + "Cert Alias is : " + alias);
if (encryptedKeyPass != null && !encryptedKeyPass.isEmpty()) {
debug.message(method + "Using provided Cert KeyPass");
}
}
PrivateKey signingKey;
if (encryptedKeyPass == null || encryptedKeyPass.isEmpty()) {
signingKey = keyProvider.getPrivateKey(alias);
} else {
signingKey = keyProvider.getPrivateKey(alias, encryptedKeyPass);
}
X509Certificate signingCert = null;
if (includeCert) {
signingCert = keyProvider.getX509Certificate(alias);
}
if (signingKey != null) {
sloResponse.sign(signingKey, signingCert);
} else {
debug.error("Incorrect configuration for Signing Certificate.");
throw new SAML2Exception(
SAML2Utils.bundle.getString("metaDataError"));
}
}
/**
* Verify the signature in LogoutResponse.
*
* @param sloResponse SLO response will be verified.
* @param realm realm of host entity.
* @param remoteEntity entity ID of remote host entity.
* @param hostEntity entity ID of host entity.
* @param hostEntityRole role of host entity.
* @return returns true if signature is valid.
* @throws SAML2Exception if error in verifying the signature.
* @throws SessionException if error in verifying the signature.
*/
public static boolean verifySLOResponse(LogoutResponse sloResponse,
String realm, String remoteEntity,
String hostEntity, String hostEntityRole)
throws SAML2Exception, SessionException {
String method = "verifySLOResponse : ";
boolean needVerifySignature =
SAML2Utils.getWantLogoutResponseSigned(realm, hostEntity,
hostEntityRole);
if (needVerifySignature == false) {
if (debug.messageEnabled()) {
debug.message(method +
"SLOResponse doesn't need to be verified.");
}
return true;
}
boolean valid = false;
X509Certificate signingCert = null;
if (hostEntityRole.equalsIgnoreCase(SAML2Constants.IDP_ROLE)) {
SPSSODescriptorElement spSSODesc =
metaManager.getSPSSODescriptor(realm, remoteEntity);
signingCert =
KeyUtil.getVerificationCert(spSSODesc, remoteEntity,
SAML2Constants.SP_ROLE);
} else {
IDPSSODescriptorElement idpSSODesc =
metaManager.getIDPSSODescriptor(realm, remoteEntity);
signingCert =
KeyUtil.getVerificationCert(idpSSODesc, remoteEntity,
SAML2Constants.IDP_ROLE);
}
if (signingCert != null) {
valid = sloResponse.isSignatureValid(signingCert);
if (debug.messageEnabled()) {
debug.message(method + "Signature is : " + valid);
}
} else {
debug.error("Incorrect configuration for Signing Certificate.");
throw new SAML2Exception(
SAML2Utils.bundle.getString("metaDataError"));
}
return valid;
}
static void setNameIDForSLORequest(LogoutRequest request,
NameID nameID, String realm, String hostEntity,
String hostEntityRole, String remoteEntity)
throws SAML2Exception, SessionException {
String method = "setNameIDForSLORequest: ";
boolean needEncryptIt = false;
if (hostEntityRole.equalsIgnoreCase(SAML2Constants.IDP_ROLE)) {
needEncryptIt =
SAML2Utils.getWantNameIDEncrypted(realm, remoteEntity,
SAML2Constants.SP_ROLE);
} else {
needEncryptIt =
SAML2Utils.getWantNameIDEncrypted(realm, remoteEntity,
SAML2Constants.IDP_ROLE);
}
if (needEncryptIt == false) {
if (debug.messageEnabled()) {
debug.message(method + "NamID doesn't need to be encrypted.");
}
request.setNameID(nameID);
return;
}
EncInfo encryptInfo = null;
KeyDescriptorType keyDescriptor = null;
if (hostEntityRole.equalsIgnoreCase(SAML2Constants.IDP_ROLE)) {
SPSSODescriptorElement spSSODesc =
metaManager.getSPSSODescriptor(realm, remoteEntity);
keyDescriptor = KeyUtil.getKeyDescriptor(spSSODesc, "encryption");
encryptInfo = KeyUtil.getEncInfo(spSSODesc, remoteEntity,
SAML2Constants.SP_ROLE);
} else {
IDPSSODescriptorElement idpSSODesc =
metaManager.getIDPSSODescriptor(realm, remoteEntity);
keyDescriptor = KeyUtil.getKeyDescriptor(idpSSODesc, "encryption");
encryptInfo = KeyUtil.getEncInfo(idpSSODesc, remoteEntity,
SAML2Constants.IDP_ROLE);
}
if (debug.messageEnabled()) {
debug.message(method + "realm is : "+ realm);
debug.message(method + "hostEntity is : " + hostEntity);
debug.message(method + "Host Entity role is : " + hostEntityRole);
debug.message(method + "remoteEntity is : " + remoteEntity);
}
if (encryptInfo == null) {
debug.error("NO meta data for encrypt Info.");
throw new SAML2Exception(
SAML2Utils.bundle.getString("metaDataError"));
}
X509Certificate certificate = KeyUtil.getCert(keyDescriptor);
PublicKey recipientPublicKey = certificate.getPublicKey();
EncryptedID encryptedID = nameID.encrypt(recipientPublicKey,
encryptInfo.getDataEncAlgorithm(),
encryptInfo.getDataEncStrength(),
remoteEntity);
request.setEncryptedID(encryptedID);
}
static NameID getNameIDFromSLORequest(LogoutRequest request,
String realm, String hostEntity,
String hostEntityRole)
throws SAML2Exception {
String method = "getNameIDFromSLORequest: ";
String alias = null;
boolean needDecryptIt =
SAML2Utils.getWantNameIDEncrypted(realm,hostEntity,hostEntityRole);
if (needDecryptIt == false) {
if (debug.messageEnabled()) {
debug.message(method + "NamID doesn't need to be decrypted.");
}
return request.getNameID();
}
alias = SAML2Utils.getEncryptionCertAlias(realm, hostEntity,
hostEntityRole);
if (debug.messageEnabled()) {
debug.message(method + "realm is : "+ realm);
debug.message(method + "hostEntity is : " + hostEntity);
debug.message(method + "Host Entity role is : " + hostEntityRole);
debug.message(method + "Cert Alias is : " + alias);
}
PrivateKey privateKey = keyProvider.getPrivateKey(alias);
EncryptedID encryptedID = request.getEncryptedID();
return encryptedID.decrypt(privateKey);
}
public static void sendSLOResponse(HttpServletResponse response,
LogoutResponse sloResponse, String sloURL, String relayState,
String realm, String hostEntity, String hostEntityRole,
String remoteEntity) throws SAML2Exception {
sendSLOResponseRedirect(response, sloResponse, sloURL, relayState,
realm, hostEntity, hostEntityRole, remoteEntity);
}
public static void sendSLOResponse(HttpServletResponse response,
LogoutResponse sloResponse, String sloURL, String relayState,
String realm, String hostEntity, String hostEntityRole,
String remoteEntity, String binding) throws SAML2Exception {
if (SAML2Constants.HTTP_POST.equals(binding)) {
sendSLOResponsePost(response, sloResponse, sloURL, relayState,
realm, hostEntity, hostEntityRole, remoteEntity);
} else {
sendSLOResponseRedirect(response, sloResponse, sloURL, relayState,
realm, hostEntity, hostEntityRole, remoteEntity);
}
}
public static void sendSLOResponsePost(HttpServletResponse response,
LogoutResponse sloResponse, String sloURL, String relayState,
String realm, String hostEntity, String hostEntityRole,
String remoteEntity) throws SAML2Exception {
signSLOResponse(sloResponse, realm, hostEntity, hostEntityRole,
remoteEntity);
String logoutResponseStr = sloResponse.toXMLString(true,true);
String encMsg = SAML2Utils.encodeForPOST(logoutResponseStr);
try {
SAML2Utils.postToTarget(response, "SAMLResponse", encMsg,
"RelayState", relayState, sloURL);
} catch (IOException ioe) {
SAML2Utils.debug.error("LogoutUtil.sendSLOResponsePost:", ioe);
throw new SAML2Exception(SAML2Utils.bundle.getString(
"errorPostingLogoutResponse"));
}
}
public static void sendSLOResponseRedirect(HttpServletResponse response,
LogoutResponse sloResponse,
String sloURL,
String relayState,
String realm,
String hostEntity,
String hostEntityRole,
String remoteEntity)
throws SAML2Exception {
try {
String logoutResXMLString = sloResponse.toXMLString(true,true);
// encode the xml string
String encodedXML =
SAML2Utils.encodeForRedirect(logoutResXMLString);
StringBuffer queryString =
new StringBuffer().append(SAML2Constants.SAML_RESPONSE)
.append(SAML2Constants.EQUAL)
.append(encodedXML);
if (relayState != null && relayState.length() > 0
&& relayState.getBytes("UTF-8").length <= 80) {
queryString.append("&").append(SAML2Constants.RELAY_STATE)
.append("=").append(URLEncDec.encode(relayState));
}
boolean needToSign = false;
if (hostEntityRole.equalsIgnoreCase(SAML2Constants.IDP_ROLE)) {
needToSign =
SAML2Utils.getWantLogoutResponseSigned(realm, remoteEntity,
SAML2Constants.SP_ROLE);
} else {
needToSign =
SAML2Utils.getWantLogoutResponseSigned(realm, remoteEntity,
SAML2Constants.IDP_ROLE);
}
String signedQueryString = queryString.toString();
if (needToSign == true) {
signedQueryString =
SAML2Utils.signQueryString(signedQueryString, realm,
hostEntity, hostEntityRole);
}
String redirectURL = sloURL+ (sloURL.contains("?") ? "&" : "?") +
signedQueryString;
if (debug.messageEnabled()) {
debug.message("redirectURL :" + redirectURL);
}
String[] data = {sloURL};
LogUtil.access(Level.INFO,LogUtil.REDIRECT_TO_SP,data,
null);
response.sendRedirect(redirectURL);
} catch (Exception e) {
debug.error("Exception :",e);
throw new SAML2Exception(SAML2Utils.bundle.getString(
"errorRedirectingLogoutResponse"));
}
}
/**
* Returns binding information of SLO Service for remote entity
* from request or meta configuration.
*
* @param request the HttpServletRequest.
* @param metaAlias entityID of hosted entity.
* @param hostEntityRole Role of hosted entity.
* @param remoteEntityID entityID of remote entity.
* @return return true if the processing is successful.
* @throws SAML2Exception if no binding information is configured.
*/
public static String getSLOBindingInfo(HttpServletRequest request,
String metaAlias,
String hostEntityRole,
String remoteEntityID)
throws SAML2Exception {
String binding = request.getParameter(SAML2Constants.BINDING);
try {
if (binding == null) {
String realm = SAML2MetaUtils.getRealmByMetaAlias(metaAlias);
SingleLogoutServiceElement sloService =
getSLOServiceElement(realm, remoteEntityID,
hostEntityRole, null);
if (sloService != null) {
binding = sloService.getBinding();
}
}
} catch (SessionException e) {
debug.error("Invalid SSOToken", e);
throw new SAML2Exception(
SAML2Utils.bundle.getString("metaDataError"));
}
if (binding == null) {
debug.error("Incorrect configuration for SingleLogout Service.");
throw new SAML2Exception(
SAML2Utils.bundle.getString("metaDataError"));
}
return binding;
}
private static SingleLogoutServiceElement getSLOServiceElement(
String realm, String entityID,
String hostEntityRole, String binding)
throws SAML2MetaException, SessionException, SAML2Exception {
SingleLogoutServiceElement sloService = null;
String method = "getSLOServiceElement: ";
if (debug.messageEnabled()) {
debug.message(method + "Realm : " + realm);
debug.message(method + "Entity ID : " + entityID);
debug.message(method + "Host Entity Role : " + hostEntityRole);
}
if (hostEntityRole.equalsIgnoreCase(SAML2Constants.SP_ROLE)) {
sloService = getIDPSLOConfig(realm, entityID, binding);
} else if (hostEntityRole.equalsIgnoreCase(SAML2Constants.IDP_ROLE)){
sloService = getSPSLOConfig(realm, entityID, binding);
} else {
debug.error("Hosted Entity role is missing .");
throw new SAML2Exception(
SAML2Utils.bundle.getString("nullIDPEntityID"));
}
return sloService;
}
/**
* Returns first SingleLogout configuration in an entity under
* the realm.
* @param realm The realm under which the entity resides.
* @param entityId ID of the entity to be retrieved.
* @param binding bind type need to has to be matched.
* @return <code>SingleLogoutServiceElement</code> for the entity or null
* @throws SAML2MetaException if unable to retrieve the first identity
* provider's SSO configuration.
* @throws SessionException invalid or expired single-sign-on session
*/
static public SingleLogoutServiceElement getIDPSLOConfig(
String realm,
String entityId,
String binding)
throws SAML2MetaException, SessionException {
SingleLogoutServiceElement slo = null;
IDPSSODescriptorElement idpSSODesc =
metaManager.getIDPSSODescriptor(realm, entityId);
if (idpSSODesc == null) {
debug.error("Identity Provider SSO config is missing.");
return null;
}
List list = idpSSODesc.getSingleLogoutService();
if ((list != null) && !list.isEmpty()) {
if (binding == null) {
return (SingleLogoutServiceElement)list.get(0);
}
Iterator it = list.iterator();
while (it.hasNext()) {
slo = (SingleLogoutServiceElement)it.next();
if (binding.equalsIgnoreCase(slo.getBinding())) {
break;
}
}
}
return slo;
}
/**
* Returns first SingleLogout configuration in an entity under
* the realm.
* @param realm The realm under which the entity resides.
* @param entityId ID of the entity to be retrieved.
* @param binding bind type need to has to be matched.
* @return <code>SingleLogoutServiceElement</code> for the entity or null
* @throws SAML2MetaException if unable to retrieve the first identity
* provider's SSO configuration.
* @throws SessionException invalid or expired single-sign-on session
*/
static public SingleLogoutServiceElement getSPSLOConfig(
String realm, String entityId,
String binding)
throws SAML2MetaException, SessionException {
SingleLogoutServiceElement slo = null;
SPSSODescriptorElement spSSODesc =
metaManager.getSPSSODescriptor(realm, entityId);
if (spSSODesc == null) {
return null;
}
List list = spSSODesc.getSingleLogoutService();
if ((list != null) && !list.isEmpty()) {
if (binding == null) {
return (SingleLogoutServiceElement)list.get(0);
}
Iterator it = list.iterator();
while (it.hasNext()) {
slo = (SingleLogoutServiceElement)it.next();
if (binding.equalsIgnoreCase(slo.getBinding())) {
break;
}
}
}
return slo;
}
/**
* Returns the extensions list
* @param paramsMap request paramsMap has extensions
* @return <code>List</code> for extensions params
*/
public static List getExtensionsList(Map paramsMap) {
List extensionsList = null;
// TODO Get the Extensions list from request parameter
return extensionsList;
}
private static void doSLOByPOST(String requestID,
String sloRequestXMLString, String sloURL, String relayState,
String realm, String hostEntity, String hostRole,
HttpServletResponse response) throws SAML2Exception, SessionException {
if (debug.messageEnabled()) {
debug.message("LogoutUtil.doSLOByPOST : SLORequestXML: "
+ sloRequestXMLString + "\nPOSTURL : " + sloURL);
debug.message("LogoutUtil.doSLOByPOST : relayState : "
+ sloRequestXMLString + "\nPOSTURL : " + relayState);
}
String encMsg = SAML2Utils.encodeForPOST(sloRequestXMLString);
try {
SAML2Utils.postToTarget(response, "SAMLRequest", encMsg,
"RelayState", relayState, sloURL);
} catch (Exception e) {
debug.error("LogoutUtil.doSLOByPOST:", e);
throw new SAML2Exception(SAML2Utils.bundle.getString(
"postToTargetFailed"));
}
}
static LogoutResponse getLogoutResponseFromPost(String samlResponse,
HttpServletResponse response) throws SAML2Exception {
if (samlResponse == null) {
throw new SAML2Exception(SAML2Utils.bundle.getString(
"missingLogoutResponse"));
}
LogoutResponse resp = null;
ByteArrayInputStream bis = null;
try {
byte[] raw = Base64.decode(samlResponse);
if (raw != null) {
bis = new ByteArrayInputStream(raw);
Document doc = XMLUtils.toDOMDocument(bis, debug);
if (doc != null) {
resp = ProtocolFactory.getInstance().
createLogoutResponse(doc.getDocumentElement());
}
}
} catch (Exception e) {
debug.error("LogoutUtil.getLogoutResponseFromPost:", e);
} finally {
if (bis != null) {
try {
bis.close();
} catch (Exception ie) {
if (debug.messageEnabled()) {
debug.message("LogoutUtil.getLogoutResponseFromPost:",
ie);
}
}
}
}
if (resp == null) {
throw new SAML2Exception(SAML2Utils.bundle.getString(
"errorGettingLogoutResponse"));
}
return resp;
}
static LogoutRequest getLogoutRequestFromPost(String samlRequest,
HttpServletResponse response) throws SAML2Exception {
if (samlRequest == null) {
throw new SAML2Exception(SAML2Utils.bundle.getString(
"missingLogoutRequest"));
}
LogoutRequest req = null;
ByteArrayInputStream bis = null;
try {
byte[] raw = Base64.decode(samlRequest);
if (raw != null) {
bis = new ByteArrayInputStream(raw);
Document doc = XMLUtils.toDOMDocument(bis, debug);
if (doc != null) {
req = ProtocolFactory.getInstance().
createLogoutRequest(doc.getDocumentElement());
}
}
} catch (Exception e) {
debug.error("LogoutUtil.getLogoutRequestFromPost:", e);
} finally {
if (bis != null) {
try {
bis.close();
} catch (Exception ie) {
if (debug.messageEnabled()) {
debug.message("LogoutUtil.getLogoutRequestFromPost:",
ie);
}
}
}
}
if (req == null) {
throw new SAML2Exception(SAML2Utils.bundle.getString(
"errorGettingLogoutRequest"));
}
return req;
}
}