/*
* Copyright (c) 2005, 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.authenticator.saml2.sso.util;
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.opensaml.Configuration;
import org.opensaml.DefaultBootstrap;
import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.xml.ConfigurationException;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.io.Unmarshaller;
import org.opensaml.xml.io.UnmarshallerFactory;
import org.opensaml.xml.io.UnmarshallingException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.wso2.carbon.base.MultitenantConstants;
import org.wso2.carbon.core.util.KeyStoreManager;
import org.wso2.carbon.identity.authenticator.saml2.sso.SAML2SSOAuthenticatorException;
import org.wso2.carbon.identity.authenticator.saml2.sso.internal.SAML2SSOAuthBEDataHolder;
import org.wso2.carbon.user.core.service.RealmService;
import org.xml.sax.SAXException;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
public class Util {
private Util(){
}
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);
/**
* Constructing the XMLObject Object from a String
*
* @param authReqStr
* @return Corresponding XMLObject which is a SAML2 object
* @throws org.wso2.carbon.identity.authenticator.saml2.sso.SAML2SSOAuthenticatorException
*/
public static XMLObject unmarshall(String authReqStr) throws SAML2SSOAuthenticatorException {
XMLObject response;
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);
response = unmarshaller.unmarshall(element);
// Check for duplicate samlp:Response
NodeList list = response.getDOM().getElementsByTagNameNS(SAMLConstants.SAML20P_NS, "Response");
if (list.getLength() > 0) {
log.error("Invalid schema for the SAML2 reponse");
throw new SAML2SSOAuthenticatorException("Error occured while processing saml2 response");
}
return response;
} catch (ParserConfigurationException e) {
log.error("Error occured while processing saml2 response");
throw new SAML2SSOAuthenticatorException("Error occured while processing saml2 response",e);
} catch (SAXException e) {
log.error("Error occured while processing saml2 response");
throw new SAML2SSOAuthenticatorException("Error occured while processing saml2 response",e);
} catch (IOException e) {
log.error("Error occured while processing saml2 response");
throw new SAML2SSOAuthenticatorException("Error occured while processing saml2 response",e);
} catch (UnmarshallingException e) {
log.error("Error occured while processing saml2 response");
throw new SAML2SSOAuthenticatorException("Error occured while processing saml2 response",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);
}
}
}
/**
* Get the X509CredentialImpl object for a particular tenant
*
* @param domainName domain name
* @return X509CredentialImpl object containing the public certificate of that tenant
* @throws org.wso2.carbon.identity.authenticator.saml2.sso.SAML2SSOAuthenticatorException Error when creating X509CredentialImpl object
*/
public static X509CredentialImpl getX509CredentialImplForTenant(String domainName)
throws SAML2SSOAuthenticatorException {
int tenantID = MultitenantConstants.SUPER_TENANT_ID;
RealmService realmService = SAML2SSOAuthBEDataHolder.getInstance().getRealmService();
// get the tenantID
if (!domainName.equals(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME)) {
try {
tenantID = realmService.getTenantManager().getTenantId(domainName);
} catch (org.wso2.carbon.user.api.UserStoreException e) {
String errorMsg = "Error getting the TenantID for the domain name";
log.error(errorMsg, e);
throw new SAML2SSOAuthenticatorException(errorMsg, e);
}
}
KeyStoreManager keyStoreManager = null;
// get an instance of the corresponding Key Store Manager instance
keyStoreManager = KeyStoreManager.getInstance(tenantID);
X509CredentialImpl credentialImpl = null;
try {
if (tenantID != MultitenantConstants.SUPER_TENANT_ID) {
// for non zero tenants, load private key from their generated key store
KeyStore keystore = keyStoreManager.getKeyStore(generateKSNameFromDomainName(domainName));
java.security.cert.X509Certificate cert =
(java.security.cert.X509Certificate) keystore.getCertificate(domainName);
credentialImpl = new X509CredentialImpl(cert);
} else { // for tenant zero, load the cert corresponding to given alias in authenticators.xml
String alias = SAML2SSOAuthBEDataHolder.getInstance().getIdPCertAlias();
java.security.cert.X509Certificate cert = null;
if (alias != null) {
cert = (X509Certificate) keyStoreManager.getPrimaryKeyStore().getCertificate(alias);
if (cert == null) {
String errorMsg = "Cannot find a certificate with the alias " + alias +
" in the default key store. Please check the 'KeyAlias' property in" +
" the SSO configuration of the authenticators.xml";
log.error(errorMsg);
throw new SAML2SSOAuthenticatorException(errorMsg);
}
} else { // if the idpCertAlias is not given, use the default certificate.
cert = keyStoreManager.getDefaultPrimaryCertificate();
}
credentialImpl = new X509CredentialImpl(cert);
}
} catch (Exception e) {
String errorMsg = "Error instantiating an X509CredentialImpl object for the public cert.";
log.error(errorMsg, e);
throw new SAML2SSOAuthenticatorException(errorMsg, e);
}
return credentialImpl;
}
/**
* Generate the key store name from the domain name
*
* @param tenantDomain tenant domain name
* @return key store file name
*/
private static String generateKSNameFromDomainName(String tenantDomain) {
String ksName = tenantDomain.trim().replace(".", "-");
return (ksName + ".jks");
}
}