/* * Copyright (c) 2005, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * Licensed 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.authenticator.saml2.sso.common; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.xerces.impl.Constants; import org.apache.xerces.util.SecurityManager; import org.apache.xml.security.c14n.Canonicalizer; import org.opensaml.Configuration; import org.opensaml.DefaultBootstrap; import org.opensaml.saml2.core.Assertion; import org.opensaml.saml2.core.Attribute; import org.opensaml.saml2.core.AttributeStatement; import org.opensaml.saml2.core.AuthnRequest; import org.opensaml.saml2.core.LogoutRequest; import org.opensaml.saml2.core.Response; import org.opensaml.xml.ConfigurationException; import org.opensaml.xml.XMLObject; import org.opensaml.xml.XMLObjectBuilder; import org.opensaml.xml.io.Marshaller; import org.opensaml.xml.io.MarshallerFactory; import org.opensaml.xml.io.Unmarshaller; import org.opensaml.xml.io.UnmarshallerFactory; import org.opensaml.xml.security.x509.X509Credential; import org.opensaml.xml.signature.KeyInfo; import org.opensaml.xml.signature.Signature; import org.opensaml.xml.signature.Signer; import org.opensaml.xml.signature.X509Certificate; import org.opensaml.xml.signature.X509Data; import org.opensaml.xml.util.Base64; import org.w3c.dom.Document; 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.core.security.AuthenticatorsConfiguration; import org.wso2.carbon.identity.authenticator.saml2.sso.common.builders.SignKeyDataHolder; import org.wso2.carbon.identity.authenticator.saml2.sso.common.util.CarbonEntityResolver; import javax.xml.XMLConstants; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.cert.CertificateEncodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; /** * This class contains all the utility methods required by SAML2 SSO Authenticator module. */ public class Util { private Util(){ } private static final char[] charMapping = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p'}; private static final String SECURITY_MANAGER_PROPERTY = Constants.XERCES_PROPERTY_PREFIX + Constants.SECURITY_MANAGER_PROPERTY; private static final int ENTITY_EXPANSION_LIMIT = 0; private static boolean bootStrapped = false; private static Log log = LogFactory.getLog(Util.class); private static Random random = new Random(); private static String serviceProviderId = null; private static String identityProviderSSOServiceURL = null; private static Map<String, String> parameters = new HashMap<String, String>(); private static String identityProviderSLOServiceURL = parameters.get( SAML2SSOAuthenticatorConstants.IDENTITY_PROVIDER_SLO_SERVICE_URL); private static String loginPage = "/carbon/admin/login.jsp"; private static String landingPage = null; private static String externalLogoutPage = null; private static boolean logoutSupportedIDP = false; private static String assertionConsumerServiceUrl = null; private static boolean initSuccess = false; private static Properties saml2IdpProperties = new Properties(); private static Map<String, String> cachedIdps = new ConcurrentHashMap<String, String>(); /** * Constructing the XMLObject Object from a String * * @param authReqStr * @return Corresponding XMLObject which is a SAML2 object * @throws SAML2SSOUIAuthenticatorException */ public static XMLObject unmarshall(String authReqStr) throws SAML2SSOUIAuthenticatorException { try { doBootstrap(); DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilderFactory.setNamespaceAware(true); documentBuilderFactory.setExpandEntityReferences(false); documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); SecurityManager securityManager = new SecurityManager(); securityManager.setEntityExpansionLimit(ENTITY_EXPANSION_LIMIT); documentBuilderFactory.setAttribute(SECURITY_MANAGER_PROPERTY, securityManager); DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder(); docBuilder.setEntityResolver(new CarbonEntityResolver()); Document document = docBuilder.parse(new ByteArrayInputStream(authReqStr.trim() .getBytes())); Element element = document.getDocumentElement(); UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory(); Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(element); return unmarshaller.unmarshall(element); } catch (Exception e) { log.error("Error in constructing AuthRequest from the encoded String", e); throw new SAML2SSOUIAuthenticatorException("Error in constructing AuthRequest from " + "the encoded String ", e); } } /** * Serializing a SAML2 object into a String * * @param xmlObject object that needs to serialized. * @return serialized object * @throws SAML2SSOUIAuthenticatorException */ public static String marshall(XMLObject xmlObject) throws SAML2SSOUIAuthenticatorException { try { doBootstrap(); 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 byteArrayOutputStrm.toString(); } catch (Exception e) { log.error("Error Serializing the SAML Response"); throw new SAML2SSOUIAuthenticatorException("Error Serializing the SAML Response", e); } } /** * Encoding the response * * @param xmlString String to be encoded * @return encoded String */ public static String encode(String xmlString) throws Exception { String encodedRequestMessage = Base64.encodeBytes(xmlString.getBytes(), Base64.DONT_BREAK_LINES); return encodedRequestMessage.trim(); } /** * Decoding and deflating the encoded AuthReq * * @param encodedStr encoded AuthReq * @return decoded AuthReq */ public static String decode(String encodedStr) throws SAML2SSOUIAuthenticatorException { try { org.apache.commons.codec.binary.Base64 base64Decoder = new org.apache.commons.codec.binary.Base64(); byte[] xmlBytes = encodedStr.getBytes("UTF-8"); byte[] base64DecodedByteArray = base64Decoder.decode(xmlBytes); return new String(base64DecodedByteArray, 0, base64DecodedByteArray.length, "UTF-8"); } catch (IOException e) { throw new SAML2SSOUIAuthenticatorException("Error when decoding the SAML Request.", e); } } /** * This method is used to initialize the OpenSAML2 library. It calls the bootstrap method, if it * is not initialized yet. */ public static void doBootstrap() { if (!bootStrapped) { try { DefaultBootstrap.bootstrap(); bootStrapped = true; } catch (ConfigurationException e) { log.error("Error in bootstrapping the OpenSAML2 library", e); } } } public static AuthnRequest setSignature(AuthnRequest authnRequest, String signatureAlgorithm, X509Credential cred) throws Exception { if (log.isDebugEnabled()) { log.debug("Signing the AuthnRequest"); } doBootstrap(); try { Signature signature = (Signature) buildXMLObject(Signature.DEFAULT_ELEMENT_NAME); signature.setSigningCredential(cred); signature.setSignatureAlgorithm(signatureAlgorithm); signature.setCanonicalizationAlgorithm(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); try { KeyInfo keyInfo = (KeyInfo) buildXMLObject(KeyInfo.DEFAULT_ELEMENT_NAME); X509Data data = (X509Data) buildXMLObject(X509Data.DEFAULT_ELEMENT_NAME); X509Certificate cert = (X509Certificate) buildXMLObject(X509Certificate.DEFAULT_ELEMENT_NAME); String value = org.apache.xml.security.utils.Base64.encode(cred .getEntityCertificate().getEncoded()); cert.setValue(value); data.getX509Certificates().add(cert); keyInfo.getX509Datas().add(data); signature.setKeyInfo(keyInfo); } catch (CertificateEncodingException e) { throw new SAML2SSOUIAuthenticatorException("errorGettingCert ",e); } authnRequest.setSignature(signature); List<Signature> signatureList = new ArrayList<Signature>(); signatureList.add(signature); // Marshall and Sign MarshallerFactory marshallerFactory = org.opensaml.xml.Configuration .getMarshallerFactory(); Marshaller marshaller = marshallerFactory.getMarshaller(authnRequest); marshaller.marshall(authnRequest); org.apache.xml.security.Init.init(); Signer.signObjects(signatureList); return authnRequest; } catch (Exception e) { throw new Exception("Error While signing the assertion.", e); } } public static LogoutRequest setSignature(LogoutRequest logoutReq, String signatureAlgorithm, SignKeyDataHolder cred) throws Exception { if (log.isDebugEnabled()) { log.debug("Signing the AuthnRequest"); } doBootstrap(); try { Signature signature = (Signature) buildXMLObject(Signature.DEFAULT_ELEMENT_NAME); signature.setSigningCredential(cred); signature.setSignatureAlgorithm(signatureAlgorithm); signature.setCanonicalizationAlgorithm(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); try { KeyInfo keyInfo = (KeyInfo) buildXMLObject(KeyInfo.DEFAULT_ELEMENT_NAME); X509Data data = (X509Data) buildXMLObject(X509Data.DEFAULT_ELEMENT_NAME); X509Certificate cert = (X509Certificate) buildXMLObject(X509Certificate.DEFAULT_ELEMENT_NAME); String value = org.apache.xml.security.utils.Base64.encode(cred .getEntityCertificate().getEncoded()); cert.setValue(value); data.getX509Certificates().add(cert); keyInfo.getX509Datas().add(data); signature.setKeyInfo(keyInfo); } catch (CertificateEncodingException e) { throw new Exception("errorGettingCert ",e); } logoutReq.setSignature(signature); List<Signature> signatureList = new ArrayList<Signature>(); signatureList.add(signature); // Marshall and Sign MarshallerFactory marshallerFactory = org.opensaml.xml.Configuration .getMarshallerFactory(); Marshaller marshaller = marshallerFactory.getMarshaller(logoutReq); marshaller.marshall(logoutReq); org.apache.xml.security.Init.init(); Signer.signObjects(signatureList); return logoutReq; } catch (Exception e) { throw new Exception("Error While signing the assertion.", e); } } public static XMLObject buildXMLObject(QName objectQName) throws Exception { XMLObjectBuilder builder = org.opensaml.xml.Configuration.getBuilderFactory().getBuilder( objectQName); if (builder == null) { throw new Exception("Unable to retrieve builder for object QName " + objectQName); } return builder.buildObject(objectQName.getNamespaceURI(), objectQName.getLocalPart(), objectQName.getPrefix()); } /** * Generates a unique Id for Authentication Requests * * @return generated unique ID */ public static String createID() { byte[] bytes = new byte[20]; // 160 bits random.nextBytes(bytes); char[] chars = new char[40]; for (int i = 0; i < bytes.length; i++) { int left = (bytes[i] >> 4) & 0x0f; int right = bytes[i] & 0x0f; chars[i * 2] = charMapping[left]; chars[i * 2 + 1] = charMapping[right]; } return String.valueOf(chars); } /** * Sets the issuerID and IDP SSO Service URL during the server start-up by reading * authenticators.xml */ public static boolean initSSOConfigParams() { AuthenticatorsConfiguration authenticatorsConfiguration = AuthenticatorsConfiguration .getInstance(); AuthenticatorsConfiguration.AuthenticatorConfig authenticatorConfig = authenticatorsConfiguration .getAuthenticatorConfig(SAML2SSOAuthenticatorConstants.AUTHENTICATOR_NAME); if (authenticatorConfig != null) { parameters = authenticatorConfig.getParameters(); serviceProviderId = parameters.get(SAML2SSOAuthenticatorConstants.SERVICE_PROVIDER_ID); identityProviderSSOServiceURL = parameters .get(SAML2SSOAuthenticatorConstants.IDENTITY_PROVIDER_SSO_SERVICE_URL); identityProviderSLOServiceURL = parameters .get(SAML2SSOAuthenticatorConstants.IDENTITY_PROVIDER_SLO_SERVICE_URL); loginPage = parameters.get(SAML2SSOAuthenticatorConstants.LOGIN_PAGE); landingPage = parameters.get(SAML2SSOAuthenticatorConstants.LANDING_PAGE); externalLogoutPage = parameters.get(SAML2SSOAuthenticatorConstants.EXTERNAL_LOGOUT_PAGE); logoutSupportedIDP = Boolean.parseBoolean(parameters.get(SAML2SSOAuthenticatorConstants.LOGOUT_SUPPORTED_IDP)); assertionConsumerServiceUrl = parameters.get(SAML2SSOAuthenticatorConstants.ASSERTION_CONSUMER_SERVICE_URL); initSuccess = true; } return initSuccess; } /** * checks whether authenticator enable ot disable * * @return True/False */ public static boolean isAuthenticatorEnabled() { AuthenticatorsConfiguration authenticatorsConfiguration = AuthenticatorsConfiguration .getInstance(); AuthenticatorsConfiguration.AuthenticatorConfig authenticatorConfig = authenticatorsConfiguration .getAuthenticatorConfig(SAML2SSOAuthenticatorConstants.AUTHENTICATOR_NAME); // if the authenticator is disabled, then do not register the servlet filter. return !authenticatorConfig.isDisabled(); } /** * returns the service provider ID of a particular server * * @return service provider ID */ public static String getServiceProviderId() { if (!initSuccess) { initSSOConfigParams(); } return serviceProviderId; } /** * returns the Identity Provider SSO Service URL * * @return dentity Provider SSO Service URL */ public static String getIdentityProviderSSOServiceURL() { if (!initSuccess) { initSSOConfigParams(); } return identityProviderSSOServiceURL; } /** * returns the Identity Provider SSO Service URL * * @return dentity Provider SSO Service URL */ public static String getIdentityProviderSLOServiceURL() { if (!initSuccess) { initSSOConfigParams(); } return identityProviderSLOServiceURL; } /** * returns the Assertion Consumer Service URL * * @return Assertion Consumer Service URL */ public static String getAssertionConsumerServiceURL() { if (!initSuccess) { initSSOConfigParams(); } return assertionConsumerServiceUrl; } /** * @param federatedDomain * @return */ public static String getIdentityProviderSSOServiceURL(String federatedDomain) { if (!initSuccess) { initSSOConfigParams(); } String fedeartedIdp = null; if (federatedDomain == null) { return null; } String selfDomain = parameters.get("IdpSelfDomain"); federatedDomain = federatedDomain.trim().toUpperCase(); if (selfDomain != null && selfDomain.trim().toUpperCase().equals(federatedDomain)) { return null; } fedeartedIdp = cachedIdps.get(federatedDomain); if (federatedDomain == null) { fedeartedIdp = parameters.get("Federated_IdP_" + federatedDomain); } if (fedeartedIdp == null) { fedeartedIdp = saml2IdpProperties.getProperty(federatedDomain); } if (log.isDebugEnabled()) { log.debug("Federated domain : " + fedeartedIdp); } if (fedeartedIdp != null) { cachedIdps.put(federatedDomain, fedeartedIdp); } return fedeartedIdp; } /** * Gets the login page URL that needs to be filtered. * * @return login page URL. */ public static String getLoginPage() { return loginPage; } /** * Returns the landing page to which the login requests will be redirected to. * * @return URL of the landing page */ public static String getLandingPage() { return landingPage; } /** * Returns the external logout page url, to which user-agent is redirected * after invalidating the local carbon session * * @return */ public static String getExternalLogoutPage() { return externalLogoutPage; } /** * Returns whether IDP supported for logout or not * used while redirecting to external logout page * @return */ public static boolean isLogoutSupportedIDP() { return logoutSupportedIDP; } /** * Returns the login attribute name which use to get the username from the SAML2 Response. * * @return Name of the login attribute */ public static String getLoginAttributeName() { if (!initSuccess) { initSSOConfigParams(); } return parameters.get(SAML2SSOAuthenticatorConstants.LOGIN_ATTRIBUTE_NAME); } /** * Get the username from the SAML2 XMLObject * * @param xmlObject SAML2 XMLObject * @return username */ public static String getUsername(XMLObject xmlObject) { if (xmlObject instanceof Response) { return getUsernameFromResponse((Response) xmlObject); } else if (xmlObject instanceof Assertion) { return getUsernameFromAssertion((Assertion) xmlObject); } else { return null; } } /** * Get the username from the SAML2 Response * * @param response SAML2 Response * @return username username contained in the SAML Response */ public static String getUsernameFromResponse(Response response) { List<Assertion> assertions = response.getAssertions(); Assertion assertion = null; if (assertions != null && assertions.size() > 0) { // There can be only one assertion in a SAML Response, so get the // first one assertion = assertions.get(0); return getUsernameFromAssertion(assertion); } return null; } /** * Get the username from the SAML2 Assertion * * @param assertion SAML2 assertion * @return username */ public static String getUsernameFromAssertion(Assertion assertion) { String loginAttributeName = getLoginAttributeName(); if (loginAttributeName != null) { // There can be multiple AttributeStatements in Assertion List<AttributeStatement> attributeStatements = assertion .getAttributeStatements(); if (attributeStatements != null) { for (AttributeStatement attributeStatement : attributeStatements) { // There can be multiple Attributes in a // attributeStatement List<Attribute> attributes = attributeStatement .getAttributes(); if (attributes != null) { for (Attribute attribute : attributes) { String attributeName = attribute.getDOM() .getAttribute("Name"); if (attributeName.equals(loginAttributeName)) { List<XMLObject> attributeValues = attribute .getAttributeValues(); // There can be multiple attribute values in // a attribute, but get the first one return attributeValues.get(0).getDOM() .getTextContent(); } } } } } } return assertion.getSubject().getNameID().getValue(); } }