org.wso2.carbon.identity.query.saml.util.OpenSAML3Util.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.carbon.identity.query.saml.util.OpenSAML3Util.java

Source

/*
 * Copyright (c) 2016, 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.query.saml.util;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xml.security.Init;
import org.apache.xml.security.c14n.Canonicalizer;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.opensaml.core.xml.io.Marshaller;
import org.opensaml.core.xml.io.MarshallerFactory;
import org.opensaml.core.xml.io.MarshallingException;
import org.opensaml.saml.common.SAMLObjectContentReference;
import org.opensaml.saml.saml2.core.Assertion;
import org.opensaml.saml.saml2.core.Issuer;
import org.opensaml.saml.saml2.core.RequestAbstractType;
import org.opensaml.saml.saml2.core.Response;
import org.opensaml.saml.saml2.core.impl.IssuerBuilder;
import org.opensaml.security.x509.X509Credential;
import org.opensaml.xmlsec.signature.KeyInfo;
import org.opensaml.xmlsec.signature.SignableXMLObject;
import org.opensaml.xmlsec.signature.Signature;
import org.opensaml.xmlsec.signature.X509Certificate;
import org.opensaml.xmlsec.signature.X509Data;
import org.opensaml.xmlsec.signature.support.SignatureException;
import org.opensaml.xmlsec.signature.support.SignatureValidator;
import org.opensaml.xmlsec.signature.support.Signer;
import org.wso2.carbon.core.util.KeyStoreManager;
import org.wso2.carbon.identity.application.common.model.FederatedAuthenticatorConfig;
import org.wso2.carbon.identity.application.common.model.IdentityProvider;
import org.wso2.carbon.identity.application.common.model.SAML2SSOFederatedAuthenticatorConfig;
import org.wso2.carbon.identity.application.common.util.IdentityApplicationConstants;
import org.wso2.carbon.identity.base.IdentityConstants;
import org.wso2.carbon.identity.base.IdentityException;
import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
import org.wso2.carbon.identity.core.util.IdentityUtil;
import org.wso2.carbon.identity.query.saml.X509CredentialImpl;
import org.wso2.carbon.identity.query.saml.exception.IdentitySAML2QueryException;
import org.wso2.carbon.identity.query.saml.internal.SAMLQueryServiceComponent;
import org.wso2.carbon.identity.sso.saml.SAMLSSOConstants;
import org.wso2.carbon.identity.sso.saml.exception.IdentitySAML2SSOException;
import org.wso2.carbon.idp.mgt.IdentityProviderManagementException;
import org.wso2.carbon.idp.mgt.IdentityProviderManager;
import org.wso2.carbon.user.api.UserStoreException;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;

import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.cert.CertificateEncodingException;
import java.util.ArrayList;
import java.util.List;

import static org.opensaml.core.xml.util.XMLObjectSupport.buildXMLObject;
import static org.wso2.carbon.identity.sso.saml.util.SAMLSSOUtil.generateKSNameFromDomainName;

/**
 * This is a utility class for processing request message issuer and signature elements.
 */
public class OpenSAML3Util {

    private static final Log log = LogFactory.getLog(OpenSAML3Util.class);

    /**
     * this method is used to get issuer according to tenant domain.
     *
     * @param tenantDomain tenant domain of the issuer
     * @return Issuer  instance of Issuer
     * @throws IdentitySAML2QueryException If unable to connect with RealmService
     */
    public static Issuer getIssuer(String tenantDomain) throws IdentitySAML2QueryException {

        Issuer issuer = new IssuerBuilder().buildObject();
        String idPEntityId = null;
        IdentityProvider identityProvider;
        int tenantId;

        if (StringUtils.isEmpty(tenantDomain) || "null".equals(tenantDomain)) {
            tenantDomain = MultitenantConstants.SUPER_TENANT_DOMAIN_NAME;
            tenantId = MultitenantConstants.SUPER_TENANT_ID;
        } else {
            try {
                tenantId = SAMLQueryServiceComponent.getRealmservice().getTenantManager().getTenantId(tenantDomain);
            } catch (UserStoreException e) {
                log.error("Error occurred while retrieving tenant id from tenant domain", e);
                throw new IdentitySAML2QueryException(
                        "Error occurred while retrieving tenant id from tenant domain");
            }

            if (MultitenantConstants.INVALID_TENANT_ID == tenantId) {
                log.error("Invalid tenant domain - '" + tenantDomain + "'");
                throw new IdentitySAML2QueryException(
                        "Error occurred while retrieving tenant id from tenant domain");
            }
        }

        try {
            IdentityTenantUtil.initializeRegistry(tenantId, tenantDomain);
            identityProvider = IdentityProviderManager.getInstance().getResidentIdP(tenantDomain);
        } catch (IdentityProviderManagementException e) {
            log.error("Error occurred while retrieving Resident Identity Provider information for tenant", e);
            throw new IdentitySAML2QueryException(
                    "Error occurred while retrieving Resident Identity Provider information for tenant "
                            + tenantDomain);
        } catch (IdentityException e) {
            log.error("Error occurred while retrieving Identity Provider Information for tenant", e);
            throw new IdentitySAML2QueryException(
                    "Error occurred while retrieving Identity Provider Information for tenant");
        }
        FederatedAuthenticatorConfig[] authnConfigs = identityProvider.getFederatedAuthenticatorConfigs();
        for (FederatedAuthenticatorConfig config : authnConfigs) {
            if (IdentityApplicationConstants.Authenticator.SAML2SSO.NAME.equals(config.getName())) {
                SAML2SSOFederatedAuthenticatorConfig samlFedAuthnConfig = new SAML2SSOFederatedAuthenticatorConfig(
                        config);
                idPEntityId = samlFedAuthnConfig.getIdpEntityId();
            }
        }
        if (idPEntityId == null) {
            idPEntityId = IdentityUtil.getProperty(IdentityConstants.ServerConfig.ENTITY_ID);
        }
        issuer.setValue(idPEntityId);
        issuer.setFormat(SAMLSSOConstants.NAME_ID_POLICY_ENTITY);
        return issuer;
    }

    /**
     * This method is used to set signature to a assertion
     *
     * @param assertion          created assertion need to sign
     * @param signatureAlgorithm signature algorithm
     * @param digestAlgorithm    cryptographic hash algorithm
     * @param cred               X509Credential instance
     * @throws IdentitySAML2QueryException If unable to write signature to the assertion
     */
    public static void setSignature(Assertion assertion, String signatureAlgorithm, String digestAlgorithm,
            X509Credential cred) throws IdentitySAML2QueryException {

        try {
            doSetSignature(assertion, signatureAlgorithm, digestAlgorithm, cred);
        } catch (IdentityException e) {
            log.error("Unable to set signature to the assertion id" + assertion.getID(), e);
            throw new IdentitySAML2QueryException("Unable to set signature to the assertion id" + assertion.getID(),
                    e);
        }
    }

    /**
     * This method is used to set Signature to the Response message
     * @param response Genareated Response including zero or more assertions
     * @param signatureAlgorithm signature Algorithm
     * @param digestAlgorithm cryptographic hash algorithm
     * @param cred X509Credential instance
     * @return Response response message
     * @throws IdentitySAML2QueryException If unable to set signature to the response
     */
    public static Response setSignature(Response response, String signatureAlgorithm, String digestAlgorithm,
            X509Credential cred) throws IdentitySAML2QueryException {

        try {
            return (Response) doSetSignature(response, signatureAlgorithm, digestAlgorithm, cred);
        } catch (IdentityException e) {
            log.error("Unable to set signature to the response id:" + response.getID(), e);
            throw new IdentitySAML2QueryException("Unable to set signature to the response id:" + response.getID(),
                    e);
        }
    }

    /**
     * Generic method to sign SAML2.0 Assertion or Response
     *
     * @param request            generic xml request
     * @param signatureAlgorithm signature algorithm
     * @param digestAlgorithm    cryptographic hash algorithm
     * @param cred               X509credential instance
     * @return SignableXMLObject signed XML object
     * @throws IdentitySAML2QueryException If unable to set signature
     */
    private static SignableXMLObject doSetSignature(SignableXMLObject request, String signatureAlgorithm,
            String digestAlgorithm, X509Credential cred) throws IdentitySAML2QueryException {
        try {
            SAMLQueryRequestUtil.doBootstrap();
            return setSSOSignature(request, signatureAlgorithm, digestAlgorithm, cred);

        } catch (IdentityException e) {
            log.error("Error while signing the XML object.", e);
            throw new IdentitySAML2QueryException("Error while signing the XML object.");
        }
    }

    /**
     * This method is used to sign XML object
     *
     * @param signableXMLObject  signable XML object
     * @param signatureAlgorithm signature algorithm
     * @param digestAlgorithm    cryptographic hash algorithm
     * @param cred               X509Credential instance
     * @return SignableXMLObject signed XML object
     * @throws IdentitySAML2QueryException If unable to set signature
     */
    public static SignableXMLObject setSSOSignature(SignableXMLObject signableXMLObject, String signatureAlgorithm,
            String digestAlgorithm, X509Credential cred) throws IdentitySAML2QueryException {
        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) {
            log.error("Error occurred while retrieving encoded cert", e);
            throw new IdentitySAML2QueryException("Error occurred while retrieving encoded cert");
        }
        signableXMLObject.setSignature(signature);
        ((SAMLObjectContentReference) signature.getContentReferences().get(0)).setDigestAlgorithm(digestAlgorithm);

        List<Signature> signatureList = new ArrayList<Signature>();
        signatureList.add(signature);

        MarshallerFactory marshallerFactory = XMLObjectProviderRegistrySupport.getMarshallerFactory();
        Marshaller marshaller = marshallerFactory.getMarshaller(signableXMLObject);

        try {
            marshaller.marshall(signableXMLObject);
        } catch (MarshallingException e) {
            log.error("Unable to marshall the request", e);
            throw new IdentitySAML2QueryException("Unable to marshall the request");
        }
        Init.init();
        try {
            Signer.signObjects(signatureList);
        } catch (SignatureException e) {
            log.error("Error occurred while signing request", e);
            throw new IdentitySAML2QueryException("Error occurred while signing request");
        }
        return signableXMLObject;
    }

    /**
     * Validate the signature of an assertion
     *
     * @param request    SAML Assertion, this could be either a SAML Request or a
     *                   LogoutRequest
     * @param alias      Certificate alias against which the signature is validated.
     * @param domainName domain name of the subject
     * @return true, if the signature is valid.
     * @throws IdentitySAML2QueryException When signature is invalid or unable to load credential information
     */
    public static boolean validateXMLSignature(RequestAbstractType request, String alias, String domainName)
            throws IdentitySAML2QueryException {
        boolean isSignatureValid = false;
        if (request.getSignature() != null) {
            try {
                X509Credential cred = OpenSAML3Util.getX509CredentialImplForTenant(domainName, alias);
                SignatureValidator.validate(request.getSignature(), cred);
                return true;
            } catch (SignatureException e) {
                log.error("Unable to validate Signature of the request id:" + request.getID() + " with alias:"
                        + alias + " ,domainname: " + domainName, e);
                throw new IdentitySAML2QueryException("Unable to validate Signature of the request id:"
                        + request.getID() + " with alias:" + alias + " ,domainname: " + domainName, e);
            }
        }
        return isSignatureValid;
    }

    /**
     * Get the X509CredentialImpl object for a particular tenant
     *
     * @param tenantDomain tenant domain of the issuer
     * @param alias        alias of cert
     * @return X509CredentialImpl object containing the public certificate of that tenant
     * @throws IdentitySAML2QueryException Error when creating X509CredentialImpl object
     */
    public static X509CredentialImpl getX509CredentialImplForTenant(String tenantDomain, String alias)
            throws IdentitySAML2QueryException {
        if (tenantDomain.trim() == null || alias.trim() == null) {
            log.error("Invalid parameters; domain name : " + tenantDomain + ", " + "alias : " + alias);
        }
        int tenantId;
        try {
            tenantId = SAMLQueryServiceComponent.getRealmservice().getTenantManager().getTenantId(tenantDomain);
        } catch (org.wso2.carbon.user.api.UserStoreException e) {
            String errorMsg = "Error getting the tenant ID for the tenant domain : " + tenantDomain;
            throw new IdentitySAML2QueryException(errorMsg, e);
        }
        KeyStoreManager keyStoreManager;
        // get an instance of the corresponding Key Store Manager instance
        keyStoreManager = KeyStoreManager.getInstance(tenantId);
        X509CredentialImpl credentialImpl = null;
        KeyStore keyStore;
        try {
            if (tenantId != -1234) {// for tenants, load private key from their generated key store
                keyStore = keyStoreManager.getKeyStore(generateKSNameFromDomainName(tenantDomain));
            } else {
                // for super tenant, load the default pub. cert using the
                // config. in carbon.xml
                keyStore = keyStoreManager.getPrimaryKeyStore();
            }
            java.security.cert.X509Certificate cert = (java.security.cert.X509Certificate) keyStore
                    .getCertificate(alias);
            credentialImpl = new X509CredentialImpl(cert);

        } catch (KeyStoreException e) {
            String errorMsg = "Error instantiating an X509CredentialImpl object for the public certificate of "
                    + tenantDomain;
            log.error(errorMsg, e);
            throw new IdentitySAML2QueryException(errorMsg, e);
        } catch (Exception e) {
            //keyStoreManager throws Exception
            log.error("Unable to load key store manager for the tenant domain:" + tenantDomain, e);
            throw new IdentitySAML2QueryException(
                    "Unable to load key store manager for the tenant domain:" + tenantDomain, e);
        }
        return credentialImpl;
    }

}