/*
* Copyright (c) 2012, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*
*/
package org.wso2.carbon.identity.sso.agent.saml;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xml.security.signature.XMLSignature;
import org.joda.time.DateTime;
import org.opensaml.Configuration;
import org.opensaml.common.SAMLVersion;
import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.saml2.common.Extensions;
import org.opensaml.saml2.core.Assertion;
import org.opensaml.saml2.core.Attribute;
import org.opensaml.saml2.core.AttributeStatement;
import org.opensaml.saml2.core.Audience;
import org.opensaml.saml2.core.AudienceRestriction;
import org.opensaml.saml2.core.AuthnContextClassRef;
import org.opensaml.saml2.core.AuthnContextComparisonTypeEnumeration;
import org.opensaml.saml2.core.AuthnRequest;
import org.opensaml.saml2.core.Conditions;
import org.opensaml.saml2.core.EncryptedAssertion;
import org.opensaml.saml2.core.Issuer;
import org.opensaml.saml2.core.LogoutRequest;
import org.opensaml.saml2.core.LogoutResponse;
import org.opensaml.saml2.core.NameID;
import org.opensaml.saml2.core.NameIDPolicy;
import org.opensaml.saml2.core.RequestAbstractType;
import org.opensaml.saml2.core.RequestedAuthnContext;
import org.opensaml.saml2.core.Response;
import org.opensaml.saml2.core.SessionIndex;
import org.opensaml.saml2.core.StatusCode;
import org.opensaml.saml2.core.impl.AuthnContextClassRefBuilder;
import org.opensaml.saml2.core.impl.AuthnRequestBuilder;
import org.opensaml.saml2.core.impl.IssuerBuilder;
import org.opensaml.saml2.core.impl.LogoutRequestBuilder;
import org.opensaml.saml2.core.impl.NameIDBuilder;
import org.opensaml.saml2.core.impl.NameIDPolicyBuilder;
import org.opensaml.saml2.core.impl.RequestedAuthnContextBuilder;
import org.opensaml.saml2.core.impl.SessionIndexBuilder;
import org.opensaml.saml2.ecp.RelayState;
import org.opensaml.saml2.encryption.Decrypter;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.encryption.EncryptedKey;
import org.opensaml.xml.io.Marshaller;
import org.opensaml.xml.io.MarshallerFactory;
import org.opensaml.xml.io.MarshallingException;
import org.opensaml.xml.security.SecurityHelper;
import org.opensaml.xml.security.credential.Credential;
import org.opensaml.xml.security.keyinfo.KeyInfoCredentialResolver;
import org.opensaml.xml.security.keyinfo.StaticKeyInfoCredentialResolver;
import org.opensaml.xml.signature.SignatureValidator;
import org.opensaml.xml.util.Base64;
import org.opensaml.xml.util.XMLHelper;
import org.opensaml.xml.validation.ValidationException;
import org.w3c.dom.Element;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;
import org.wso2.carbon.identity.sso.agent.SSOAgentConstants;
import org.wso2.carbon.identity.sso.agent.SSOAgentDataHolder;
import org.wso2.carbon.identity.sso.agent.SSOAgentException;
import org.wso2.carbon.identity.sso.agent.bean.LoggedInSessionBean;
import org.wso2.carbon.identity.sso.agent.bean.SSOAgentConfig;
import org.wso2.carbon.identity.sso.agent.util.SAMLSignatureValidator;
import org.wso2.carbon.identity.sso.agent.util.SSOAgentUtils;
import org.apache.commons.collections.CollectionUtils;
import javax.crypto.SecretKey;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
/**
* TODO: Need to have mechanism to map SP initiated SAML2 Request to SAML2 Responses and validate.
* TODO: Still however IdP initiated SSO also should be possible through configuration
*/
public class SAML2SSOManager {
private static final Log log = LogFactory.getLog(SAML2SSOManager.class);
private static final Logger LOGGER = Logger.getLogger(SSOAgentConstants.LOGGER_NAME);
private SSOAgentConfig ssoAgentConfig = null;
public SAML2SSOManager(SSOAgentConfig ssoAgentConfig) throws SSOAgentException {
/* Initializing the OpenSAML library, loading default configurations */
this.ssoAgentConfig = ssoAgentConfig;
//load custom Signature Validator Class
String signerClassName = ssoAgentConfig.getSAML2().getSignatureValidatorImplClass();
try {
if (signerClassName != null) {
SSOAgentDataHolder.getInstance().setSignatureValidator(Class.forName(signerClassName).newInstance());
}
} catch (ClassNotFoundException e) {
throw new SSOAgentException("Error loading custom signature validator class", e);
} catch (IllegalAccessException e) {
throw new SSOAgentException("Error loading custom signature validator class", e);
} catch (InstantiationException e) {
throw new SSOAgentException("Error loading custom signature validator class", e);
}
SSOAgentUtils.doBootstrap();
}
/**
* Returns the redirection URL with the appended SAML2
* Request message
*
* @param request SAML 2 request
* @return redirectionUrl
*/
public String buildRedirectRequest(HttpServletRequest request, boolean isLogout) throws SSOAgentException {
RequestAbstractType requestMessage = null;
if (!isLogout) {
requestMessage = buildAuthnRequest(request);
} else {
LoggedInSessionBean sessionBean = (LoggedInSessionBean) request.getSession(false).
getAttribute(SSOAgentConstants.SESSION_BEAN_NAME);
if (sessionBean != null) {
requestMessage = buildLogoutRequest(sessionBean.getSAML2SSO().getSubjectId(),
sessionBean.getSAML2SSO().getSessionIndex());
} else {
throw new SSOAgentException("SLO Request can not be built. SSO Session is NULL");
}
}
String idpUrl = null;
String encodedRequestMessage = encodeRequestMessage(
requestMessage, SAMLConstants.SAML2_REDIRECT_BINDING_URI);
StringBuilder httpQueryString = new StringBuilder(
SSOAgentConstants.SAML2SSO.HTTP_POST_PARAM_SAML2_AUTH_REQ +
"=" + encodedRequestMessage);
String relayState = ssoAgentConfig.getSAML2().getRelayState();
if (relayState != null) {
try {
httpQueryString.append("&" + RelayState.DEFAULT_ELEMENT_LOCAL_NAME + "=" +
URLEncoder.encode(relayState, "UTF-8").trim());
} catch (UnsupportedEncodingException e) {
throw new SSOAgentException("Error occurred while URLEncoding " +
RelayState.DEFAULT_ELEMENT_LOCAL_NAME, e);
}
}
if (ssoAgentConfig.getSAML2().isRequestSigned()) {
SSOAgentUtils.addDeflateSignatureToHTTPQueryString(httpQueryString,
new X509CredentialImpl(ssoAgentConfig.getSAML2().getSSOAgentX509Credential()));
}
if (ssoAgentConfig.getQueryParams() != null && !ssoAgentConfig.getQueryParams().isEmpty()) {
StringBuilder builder = new StringBuilder();
for (Map.Entry<String, String[]> entry : ssoAgentConfig.getQueryParams().entrySet()) {
if (entry.getKey() != null && entry.getValue() != null && entry.getValue().length > 0) {
for (String param : entry.getValue()) {
builder.append("&").append(entry.getKey()).append("=").append(param);
}
}
}
httpQueryString.append(builder);
}
if (ssoAgentConfig.getSAML2().getIdPURL().indexOf("?") > -1) {
idpUrl = ssoAgentConfig.getSAML2().getIdPURL().concat("&").concat(httpQueryString.toString());
} else {
idpUrl = ssoAgentConfig.getSAML2().getIdPURL().concat("?").concat(httpQueryString.toString());
}
return idpUrl;
}
/**
* Handles the request for http post binding
*
* @param request The HTTP request with SAML2 message
* @param response The HTTP response
* @param isLogout Whether the request is a logout request
* @throws SSOAgentException
*/
public String buildPostRequest(HttpServletRequest request, HttpServletResponse response,
boolean isLogout) throws SSOAgentException {
RequestAbstractType requestMessage = null;
if (!isLogout) {
requestMessage = buildAuthnRequest(request);
if (ssoAgentConfig.getSAML2().isRequestSigned()) {
requestMessage = SSOAgentUtils.setSignature((AuthnRequest) requestMessage,
XMLSignature.ALGO_ID_SIGNATURE_RSA,
new X509CredentialImpl(ssoAgentConfig.getSAML2().getSSOAgentX509Credential()));
}
} else {
LoggedInSessionBean sessionBean = (LoggedInSessionBean) request.getSession(false).
getAttribute(SSOAgentConstants.SESSION_BEAN_NAME);
if (sessionBean != null) {
requestMessage = buildLogoutRequest(sessionBean.getSAML2SSO()
.getSubjectId(), sessionBean.getSAML2SSO().getSessionIndex());
if (ssoAgentConfig.getSAML2().isRequestSigned()) {
requestMessage = SSOAgentUtils.setSignature((LogoutRequest) requestMessage,
XMLSignature.ALGO_ID_SIGNATURE_RSA,
new X509CredentialImpl(ssoAgentConfig.getSAML2().getSSOAgentX509Credential()));
}
} else {
throw new SSOAgentException("SLO Request can not be built. SSO Session is null");
}
}
String encodedRequestMessage = encodeRequestMessage(requestMessage, SAMLConstants.SAML2_POST_BINDING_URI);
Map<String, String[]> paramsMap = new HashMap<String, String[]>();
paramsMap.put(SSOAgentConstants.SAML2SSO.HTTP_POST_PARAM_SAML2_AUTH_REQ,
new String[]{encodedRequestMessage});
if (ssoAgentConfig.getSAML2().getRelayState() != null) {
paramsMap.put(RelayState.DEFAULT_ELEMENT_LOCAL_NAME,
new String[]{ssoAgentConfig.getSAML2().getRelayState()});
}
//Add any additional parameters defined
if (ssoAgentConfig.getQueryParams() != null && !ssoAgentConfig.getQueryParams().isEmpty()) {
paramsMap.putAll(ssoAgentConfig.getQueryParams());
}
StringBuilder htmlParams = new StringBuilder();
for (Map.Entry<String, String[]> entry : paramsMap.entrySet()) {
if (entry.getKey() != null && entry.getValue() != null && entry.getValue().length > 0) {
for (String param : entry.getValue()) {
htmlParams.append("<input type='hidden' name='").append(entry.getKey())
.append("' value='").append(param).append("'>\n");
}
}
}
String htmlPayload = ssoAgentConfig.getSAML2().getPostBindingRequestHTMLPayload();
if (htmlPayload == null || !htmlPayload.contains("<!--$saml_params-->")) {
htmlPayload = "<html>\n" +
"<body>\n" +
"<p>You are now redirected back to " + ssoAgentConfig.getSAML2().getIdPURL() + " \n" +
"If the redirection fails, please click the post button.</p>\n" +
"<form method='post' action='" + ssoAgentConfig.getSAML2().getIdPURL() + "'>\n" +
"<p>\n" +
htmlParams.toString() +
"<button type='submit'>POST</button>\n" +
"</p>\n" +
"</form>\n" +
"<script type='text/javascript'>\n" +
"document.forms[0].submit();\n" +
"</script>\n" +
"</body>\n" +
"</html>";
} else {
htmlPayload = htmlPayload.replace("<!--$saml_params-->",
htmlParams.toString());
}
return htmlPayload;
}
public void processResponse(HttpServletRequest request, HttpServletResponse response)
throws SSOAgentException {
String saml2SSOResponse = request.getParameter(SSOAgentConstants.SAML2SSO.HTTP_POST_PARAM_SAML2_RESP);
if (saml2SSOResponse != null) {
String decodedResponse = new String(Base64.decode(saml2SSOResponse), Charset.forName("UTF-8"));
XMLObject samlObject = SSOAgentUtils.unmarshall(decodedResponse);
if (samlObject instanceof LogoutResponse) {
//This is a SAML response for a single logout request from the SP
doSLO(request);
} else {
processSSOResponse(request);
}
String relayState = request.getParameter(RelayState.DEFAULT_ELEMENT_LOCAL_NAME);
if (relayState != null && !relayState.isEmpty() && !"null".equalsIgnoreCase(relayState)) { //additional
// checks for incompetent IdPs
ssoAgentConfig.getSAML2().setRelayState(relayState);
}
} else {
throw new SSOAgentException("Invalid SAML2 Response. SAML2 Response can not be null.");
}
}
/**
* This method handles the logout requests from the IdP
* Any request for the defined logout URL is handled here
*
* @param request
* @throws javax.servlet.ServletException
* @throws IOException
*/
public void doSLO(HttpServletRequest request) throws SSOAgentException {
XMLObject saml2Object = null;
if (request.getParameter(SSOAgentConstants.SAML2SSO.HTTP_POST_PARAM_SAML2_AUTH_REQ) != null) {
saml2Object = SSOAgentUtils.unmarshall(new String(Base64.decode(request.getParameter(
SSOAgentConstants.SAML2SSO.HTTP_POST_PARAM_SAML2_AUTH_REQ)), Charset.forName("UTF-8")));
}
if (saml2Object == null) {
saml2Object = SSOAgentUtils.unmarshall(new String(Base64.decode(request.getParameter(
SSOAgentConstants.SAML2SSO.HTTP_POST_PARAM_SAML2_RESP)), Charset.forName("UTF-8")));
}
if (saml2Object instanceof LogoutRequest) {
LogoutRequest logoutRequest = (LogoutRequest) saml2Object;
String sessionIndex = logoutRequest.getSessionIndexes().get(0).getSessionIndex();
Set<HttpSession> sessions = SSOAgentSessionManager.invalidateAllSessions(sessionIndex);
for (HttpSession session : sessions) {
session.invalidate();
}
} else if (saml2Object instanceof LogoutResponse) {
if (request.getSession(false) != null) {
/**
* Not invalidating session explicitly since there may be other listeners
* still waiting to get triggered and at the end of the chain session needs to be
* invalidated by the system
*/
Set<HttpSession> sessions =
SSOAgentSessionManager.invalidateAllSessions(request.getSession(false));
for (HttpSession session : sessions) {
try {
session.invalidate();
} catch (IllegalStateException ignore) {
if (log.isDebugEnabled()) {
log.debug("Ignoring exception : ", ignore);
}
//ignore
//session is already invalidated
}
}
}
} else {
throw new SSOAgentException("Invalid SAML2 Single Logout Request/Response");
}
}
protected void processSSOResponse(HttpServletRequest request) throws SSOAgentException {
LoggedInSessionBean sessionBean = new LoggedInSessionBean();
sessionBean.setSAML2SSO(sessionBean.new SAML2SSO());
String saml2ResponseString =
new String(Base64.decode(request.getParameter(
SSOAgentConstants.SAML2SSO.HTTP_POST_PARAM_SAML2_RESP)), Charset.forName("UTF-8"));
Response saml2Response = (Response) SSOAgentUtils.unmarshall(saml2ResponseString);
sessionBean.getSAML2SSO().setResponseString(saml2ResponseString);
sessionBean.getSAML2SSO().setSAMLResponse(saml2Response);
Assertion assertion = null;
if (ssoAgentConfig.getSAML2().isAssertionEncrypted()) {
List<EncryptedAssertion> encryptedAssertions = saml2Response.getEncryptedAssertions();
EncryptedAssertion encryptedAssertion = null;
if (!CollectionUtils.isEmpty(encryptedAssertions)) {
encryptedAssertion = encryptedAssertions.get(0);
try {
assertion = getDecryptedAssertion(encryptedAssertion);
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug("Assertion decryption failure : ", e);
}
throw new SSOAgentException("Unable to decrypt the SAML2 Assertion");
}
}
} else {
List<Assertion> assertions = saml2Response.getAssertions();
if (assertions != null && !assertions.isEmpty()) {
assertion = assertions.get(0);
}
}
if (assertion == null) {
if (isNoPassive(saml2Response)) {
LOGGER.log(Level.FINE, "Cannot authenticate in passive mode");
return;
}
throw new SSOAgentException("SAML2 Assertion not found in the Response");
}
String idPEntityIdValue = assertion.getIssuer().getValue();
if (idPEntityIdValue == null || idPEntityIdValue.isEmpty()) {
throw new SSOAgentException("SAML2 Response does not contain an Issuer value");
} else if (!idPEntityIdValue.equals(ssoAgentConfig.getSAML2().getIdPEntityId())) {
throw new SSOAgentException("SAML2 Response Issuer verification failed");
}
sessionBean.getSAML2SSO().setAssertion(assertion);
// Cannot marshall SAML assertion here, before signature validation due to a weird issue in OpenSAML
// Get the subject name from the Response Object and forward it to login_action.jsp
String subject = null;
if (assertion.getSubject() != null && assertion.getSubject().getNameID() != null) {
subject = assertion.getSubject().getNameID().getValue();
}
if (subject == null) {
throw new SSOAgentException("SAML2 Response does not contain the name of the subject");
}
sessionBean.getSAML2SSO().setSubjectId(subject); // set the subject
request.getSession().setAttribute(SSOAgentConstants.SESSION_BEAN_NAME, sessionBean);
// validate audience restriction
validateAudienceRestriction(assertion);
// validate signature
validateSignature(saml2Response, assertion);
// Marshalling SAML2 assertion after signature validation due to a weird issue in OpenSAML
sessionBean.getSAML2SSO().setAssertionString(marshall(assertion));
((LoggedInSessionBean) request.getSession().getAttribute(
SSOAgentConstants.SESSION_BEAN_NAME)).getSAML2SSO().
setSubjectAttributes(getAssertionStatements(assertion));
//For removing the session when the single sign out request made by the SP itself
if (ssoAgentConfig.getSAML2().isSLOEnabled()) {
String sessionId = assertion.getAuthnStatements().get(0).getSessionIndex();
if (sessionId == null) {
throw new SSOAgentException("Single Logout is enabled but IdP Session ID not found in SAML2 Assertion");
}
((LoggedInSessionBean) request.getSession().getAttribute(
SSOAgentConstants.SESSION_BEAN_NAME)).getSAML2SSO().setSessionIndex(sessionId);
SSOAgentSessionManager.addAuthenticatedSession(request.getSession(false));
}
request.getSession().setAttribute(SSOAgentConstants.SESSION_BEAN_NAME, sessionBean);
}
protected LogoutRequest buildLogoutRequest(String user, String sessionIdx) throws SSOAgentException {
LogoutRequest logoutReq = new LogoutRequestBuilder().buildObject();
logoutReq.setID(SSOAgentUtils.createID());
logoutReq.setDestination(ssoAgentConfig.getSAML2().getIdPURL());
DateTime issueInstant = new DateTime();
logoutReq.setIssueInstant(issueInstant);
logoutReq.setNotOnOrAfter(new DateTime(issueInstant.getMillis() + 5 * 60 * 1000));
IssuerBuilder issuerBuilder = new IssuerBuilder();
Issuer issuer = issuerBuilder.buildObject();
issuer.setValue(ssoAgentConfig.getSAML2().getSPEntityId());
logoutReq.setIssuer(issuer);
NameID nameId = new NameIDBuilder().buildObject();
nameId.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:entity");
nameId.setValue(user);
logoutReq.setNameID(nameId);
SessionIndex sessionIndex = new SessionIndexBuilder().buildObject();
sessionIndex.setSessionIndex(sessionIdx);
logoutReq.getSessionIndexes().add(sessionIndex);
logoutReq.setReason("Single Logout");
return logoutReq;
}
protected AuthnRequest buildAuthnRequest(HttpServletRequest request) throws SSOAgentException {
IssuerBuilder issuerBuilder = new IssuerBuilder();
Issuer issuer =
issuerBuilder.buildObject("urn:oasis:names:tc:SAML:2.0:assertion",
"Issuer", "samlp");
issuer.setValue(ssoAgentConfig.getSAML2().getSPEntityId());
/* NameIDPolicy */
NameIDPolicyBuilder nameIdPolicyBuilder = new NameIDPolicyBuilder();
NameIDPolicy nameIdPolicy = nameIdPolicyBuilder.buildObject();
nameIdPolicy.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent");
nameIdPolicy.setSPNameQualifier("Issuer");
nameIdPolicy.setAllowCreate(true);
/* AuthnContextClass */
AuthnContextClassRefBuilder authnContextClassRefBuilder = new AuthnContextClassRefBuilder();
AuthnContextClassRef authnContextClassRef =
authnContextClassRefBuilder.buildObject("urn:oasis:names:tc:SAML:2.0:assertion",
"AuthnContextClassRef",
"saml");
authnContextClassRef.setAuthnContextClassRef("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport");
/* AuthnContex */
RequestedAuthnContextBuilder requestedAuthnContextBuilder =
new RequestedAuthnContextBuilder();
RequestedAuthnContext requestedAuthnContext = requestedAuthnContextBuilder.buildObject();
requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.EXACT);
requestedAuthnContext.getAuthnContextClassRefs().add(authnContextClassRef);
DateTime issueInstant = new DateTime();
/* Creation of AuthRequestObject */
AuthnRequestBuilder authRequestBuilder = new AuthnRequestBuilder();
AuthnRequest authRequest =
authRequestBuilder.buildObject("urn:oasis:names:tc:SAML:2.0:protocol",
"AuthnRequest", "samlp");
authRequest.setForceAuthn(ssoAgentConfig.getSAML2().isForceAuthn());
authRequest.setIsPassive(ssoAgentConfig.getSAML2().isPassiveAuthn());
authRequest.setIssueInstant(issueInstant);
authRequest.setProtocolBinding(ssoAgentConfig.getSAML2().getHttpBinding());
authRequest.setAssertionConsumerServiceURL(ssoAgentConfig.getSAML2().getACSURL());
authRequest.setIssuer(issuer);
authRequest.setNameIDPolicy(nameIdPolicy);
authRequest.setRequestedAuthnContext(requestedAuthnContext);
authRequest.setID(SSOAgentUtils.createID());
authRequest.setVersion(SAMLVersion.VERSION_20);
authRequest.setDestination(ssoAgentConfig.getSAML2().getIdPURL());
if (request.getAttribute(Extensions.LOCAL_NAME) != null) {
authRequest.setExtensions((Extensions) request.getAttribute(Extensions.LOCAL_NAME));
}
/* Requesting Attributes. This Index value is registered in the IDP */
if (ssoAgentConfig.getSAML2().getAttributeConsumingServiceIndex() != null &&
ssoAgentConfig.getSAML2().getAttributeConsumingServiceIndex().trim().length() > 0) {
authRequest.setAttributeConsumingServiceIndex(Integer.parseInt(
ssoAgentConfig.getSAML2().getAttributeConsumingServiceIndex()));
}
return authRequest;
}
protected String encodeRequestMessage(RequestAbstractType requestMessage, String binding)
throws SSOAgentException {
Marshaller marshaller = Configuration.getMarshallerFactory().getMarshaller(requestMessage);
Element authDOM = null;
try {
authDOM = marshaller.marshall(requestMessage);
StringWriter rspWrt = new StringWriter();
XMLHelper.writeNode(authDOM, rspWrt);
if (SAMLConstants.SAML2_REDIRECT_BINDING_URI.equals(binding)) {
//Compress the message, Base 64 encode and URL encode
Deflater deflater = new Deflater(Deflater.DEFLATED, true);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream
(byteArrayOutputStream, deflater);
deflaterOutputStream.write(rspWrt.toString().getBytes(Charset.forName("UTF-8")));
deflaterOutputStream.close();
String encodedRequestMessage = Base64.encodeBytes(byteArrayOutputStream
.toByteArray(), Base64.DONT_BREAK_LINES);
return URLEncoder.encode(encodedRequestMessage, "UTF-8").trim();
} else if (SAMLConstants.SAML2_POST_BINDING_URI.equals(binding)) {
return Base64.encodeBytes(rspWrt.toString().getBytes(),
Base64.DONT_BREAK_LINES);
} else {
LOGGER.log(Level.FINE, "Unsupported SAML2 HTTP Binding. Defaulting to " +
SAMLConstants.SAML2_POST_BINDING_URI);
return Base64.encodeBytes(rspWrt.toString().getBytes(),
Base64.DONT_BREAK_LINES);
}
} catch (MarshallingException e) {
throw new SSOAgentException("Error occurred while encoding SAML2 request", e);
} catch (UnsupportedEncodingException e) {
throw new SSOAgentException("Error occurred while encoding SAML2 request", e);
} catch (IOException e) {
throw new SSOAgentException("Error occurred while encoding SAML2 request", e);
}
}
/*
* Process the response and returns the results
*/
private Map<String, String> getAssertionStatements(Assertion assertion) {
Map<String, String> results = new HashMap<String, String>();
if (assertion != null && assertion.getAttributeStatements() != null) {
List<AttributeStatement> attributeStatementList = assertion.getAttributeStatements();
for (AttributeStatement statement : attributeStatementList) {
List<Attribute> attributesList = statement.getAttributes();
for (Attribute attribute : attributesList) {
Element value = attribute.getAttributeValues().get(0).getDOM();
String attributeValue = value.getTextContent();
results.put(attribute.getName(), attributeValue);
}
}
}
return results;
}
/**
* Validate the AudienceRestriction of SAML2 Response
*
* @param assertion SAML2 Assertion
* @return validity
*/
protected void validateAudienceRestriction(Assertion assertion) throws SSOAgentException {
if (assertion != null) {
Conditions conditions = assertion.getConditions();
if (conditions != null) {
List<AudienceRestriction> audienceRestrictions = conditions.getAudienceRestrictions();
if (audienceRestrictions != null && !audienceRestrictions.isEmpty()) {
boolean audienceFound = false;
for (AudienceRestriction audienceRestriction : audienceRestrictions) {
if (audienceRestriction.getAudiences() != null && !audienceRestriction.getAudiences().isEmpty()
) {
for (Audience audience : audienceRestriction.getAudiences()) {
if (ssoAgentConfig.getSAML2().getSPEntityId().equals(audience.getAudienceURI())) {
audienceFound = true;
break;
}
}
}
if (audienceFound) {
break;
}
}
if (!audienceFound) {
throw new SSOAgentException("SAML2 Assertion Audience Restriction validation failed");
}
} else {
throw new SSOAgentException("SAML2 Response doesn't contain AudienceRestrictions");
}
} else {
throw new SSOAgentException("SAML2 Response doesn't contain Conditions");
}
}
}
/**
* Validate the signature of a SAML2 Response and Assertion
*
* @param response SAML2 Response
* @return true, if signature is valid.
*/
protected void validateSignature(Response response, Assertion assertion) throws SSOAgentException {
if (SSOAgentDataHolder.getInstance().getSignatureValidator() != null) {
//Custom implemetation of signature validation
SAMLSignatureValidator signatureValidatorUtility = (SAMLSignatureValidator) SSOAgentDataHolder
.getInstance().getSignatureValidator();
signatureValidatorUtility.validateSignature(response, assertion, ssoAgentConfig);
} else {
//If custom implementation not found, Execute the default implementation
if (ssoAgentConfig.getSAML2().isResponseSigned()) {
if (response.getSignature() == null) {
throw new SSOAgentException("SAML2 Response signing is enabled, but signature element not found in SAML2 Response element");
} else {
try {
SignatureValidator validator = new SignatureValidator(
new X509CredentialImpl(ssoAgentConfig.getSAML2().getSSOAgentX509Credential()));
validator.validate(response.getSignature());
} catch (ValidationException e) {
if (log.isDebugEnabled()) {
log.debug("Validation exception : ", e);
}
throw new SSOAgentException("Signature validation failed for SAML2 Response");
}
}
}
if (ssoAgentConfig.getSAML2().isAssertionSigned()) {
if (assertion.getSignature() == null) {
throw new SSOAgentException("SAML2 Assertion signing is enabled, but signature element not found in SAML2 Assertion element");
} else {
try {
SignatureValidator validator = new SignatureValidator(
new X509CredentialImpl(ssoAgentConfig.getSAML2().getSSOAgentX509Credential()));
validator.validate(assertion.getSignature());
} catch (ValidationException e) {
if (log.isDebugEnabled()) {
log.debug("Validation exception : ", e);
}
throw new SSOAgentException("Signature validation failed for SAML2 Assertion");
}
}
}
}
}
/**
* Serialize the Auth. Request
*
* @param xmlObject
* @return serialized auth. req
*/
protected String marshall(XMLObject xmlObject) throws SSOAgentException {
try {
System.setProperty("javax.xml.parsers.DocumentBuilderFactory",
"org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
MarshallerFactory marshallerFactory =
org.opensaml.xml.Configuration.getMarshallerFactory();
Marshaller marshaller = marshallerFactory.getMarshaller(xmlObject);
Element element = marshaller.marshall(xmlObject);
ByteArrayOutputStream byteArrayOutputStrm = new ByteArrayOutputStream();
DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
DOMImplementationLS impl = (DOMImplementationLS) registry.getDOMImplementation("LS");
LSSerializer writer = impl.createLSSerializer();
LSOutput output = impl.createLSOutput();
output.setByteStream(byteArrayOutputStrm);
writer.write(element, output);
return new String(byteArrayOutputStrm.toByteArray(), Charset.forName("UTF-8"));
} catch (ClassNotFoundException e) {
throw new SSOAgentException("Error in marshalling SAML2 Assertion", e);
} catch (InstantiationException e) {
throw new SSOAgentException("Error in marshalling SAML2 Assertion", e);
} catch (MarshallingException e) {
throw new SSOAgentException("Error in marshalling SAML2 Assertion", e);
} catch (IllegalAccessException e) {
throw new SSOAgentException("Error in marshalling SAML2 Assertion", e);
}
}
/**
* Get Decrypted Assertion
*
* @param encryptedAssertion
* @return
* @throws Exception
*/
protected Assertion getDecryptedAssertion(EncryptedAssertion encryptedAssertion) throws SSOAgentException {
try {
KeyInfoCredentialResolver keyResolver = new StaticKeyInfoCredentialResolver(
new X509CredentialImpl(ssoAgentConfig.getSAML2().getSSOAgentX509Credential()));
EncryptedKey key = encryptedAssertion.getEncryptedData().
getKeyInfo().getEncryptedKeys().get(0);
Decrypter decrypter = new Decrypter(null, keyResolver, null);
SecretKey dkey = (SecretKey) decrypter.decryptKey(key, encryptedAssertion.getEncryptedData().
getEncryptionMethod().getAlgorithm());
Credential shared = SecurityHelper.getSimpleCredential(dkey);
decrypter = new Decrypter(new StaticKeyInfoCredentialResolver(shared), null, null);
decrypter.setRootInNewDocument(true);
return decrypter.decrypt(encryptedAssertion);
} catch (Exception e) {
throw new SSOAgentException("Decrypted assertion error", e);
}
}
protected boolean isNoPassive(Response response) {
return response.getStatus() != null &&
response.getStatus().getStatusCode() != null &&
response.getStatus().getStatusCode().getValue().equals(StatusCode.RESPONDER_URI) &&
response.getStatus().getStatusCode().getStatusCode() != null &&
response.getStatus().getStatusCode().getStatusCode().getValue().equals(
StatusCode.NO_PASSIVE_URI);
}
public SSOAgentConfig getSsoAgentConfig() {
return ssoAgentConfig;
}
}