com.vangent.hieos.services.sts.transactions.SAML2TokenIssueHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.vangent.hieos.services.sts.transactions.SAML2TokenIssueHandler.java

Source

/*
 * This code is subject to the HIEOS License, Version 1.0
 *
 * Copyright(c) 2011 Vangent, Inc.  All rights reserved.
 *
 * 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 com.vangent.hieos.services.sts.transactions;

import com.vangent.hieos.services.sts.model.STSRequestData;
import com.vangent.hieos.services.sts.config.STSConfig;
import com.vangent.hieos.services.sts.exception.STSException;
import com.vangent.hieos.services.sts.model.STSConstants;
import com.vangent.hieos.services.sts.util.STSUtil;
import com.vangent.hieos.xutil.xlog.client.XLogMessage;
import com.vangent.hieos.xutil.xml.XMLParser;

import java.security.KeyStore;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;

import java.util.List;
import java.util.UUID;

import javax.xml.namespace.QName;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNamespace;
import org.apache.log4j.Logger;

import org.joda.time.DateTime;

import org.opensaml.common.SAMLVersion;
import org.opensaml.saml2.core.Assertion;
import org.opensaml.saml2.core.AttributeStatement;
import org.opensaml.saml2.core.Conditions;
import org.opensaml.saml2.core.Issuer;
import org.opensaml.saml2.core.NameID;
import org.opensaml.saml2.core.Subject;
import org.opensaml.saml2.core.SubjectConfirmation;
import org.opensaml.saml2.core.SubjectConfirmationData;
import org.opensaml.saml2.core.impl.ConditionsBuilder;
import org.opensaml.xml.Namespace;
import org.opensaml.xml.security.x509.BasicX509Credential;
import org.opensaml.xml.signature.KeyInfo;
import org.opensaml.xml.signature.Signature;
import org.opensaml.xml.signature.SignatureConstants;
import org.opensaml.xml.signature.SignatureException;
import org.opensaml.xml.signature.Signer;

import org.w3c.dom.Element;

import org.opensaml.saml2.core.Attribute;
import org.opensaml.saml2.core.AuthnContext;
import org.opensaml.saml2.core.AuthnContextClassRef;
import org.opensaml.saml2.core.AuthnStatement;
import org.opensaml.saml2.core.impl.AssertionMarshaller;
import org.opensaml.xml.util.XMLHelper;

/**
 *
 * @author Bernie Thuman
 */
public class SAML2TokenIssueHandler extends SAML2TokenHandler {

    private final static Logger logger = Logger.getLogger(SAML2TokenIssueHandler.class);

    /**
     *
     * @param logMessage
     */
    public SAML2TokenIssueHandler(XLogMessage logMessage) {
        super(logMessage);
    }

    /**
     *
     * @param requestData
     * @return
     * @throws STSException
     */
    @Override
    protected OMElement handle(STSRequestData requestData) throws STSException {
        STSConfig stsConfig = requestData.getSTSConfig();

        // Get the authentication type used.
        STSConstants.AuthenticationType authenticationType = requestData.getHeaderData().getAuthenticationType();

        // Create the SAML2 Assertion.
        Assertion assertion = (Assertion) STSUtil.createXMLObject(Assertion.DEFAULT_ELEMENT_NAME);
        Namespace dsns = new Namespace("http://www.w3.org/2000/09/xmldsig#", "ds");
        assertion.addNamespace(dsns);
        Namespace xsins = new Namespace("http://www.w3.org/2001/XMLSchema-instance", "xsi");
        assertion.addNamespace(xsins);
        assertion.setVersion(SAMLVersion.VERSION_20);

        // Assign a uniquie id - prefix must be compliant with xs:ID type (reason for "ID_" prefix).
        assertion.setID("ID_" + UUID.randomUUID().toString());

        // Get created and expires date/times.
        long nowInMillis = System.currentTimeMillis();
        DateTime createdDate = new DateTime(nowInMillis - stsConfig.getNotBeforeSkew());
        DateTime expiresDate = new DateTime(nowInMillis + stsConfig.getTimeToLive());

        // Set the instant the Assertion was created.
        assertion.setIssueInstant(createdDate);

        // Add an AuthnStatement.
        assertion.getAuthnStatements().add(this.getAuthnStatement(authenticationType));

        // Set the validity period (as Conditions).
        Conditions conditions = new ConditionsBuilder().buildObject();
        conditions.setNotBefore(createdDate);
        conditions.setNotOnOrAfter(expiresDate);
        assertion.setConditions(conditions);

        // Create the Subject.
        Subject subj = (Subject) STSUtil.createXMLObject(Subject.DEFAULT_ELEMENT_NAME);
        assertion.setSubject(subj);

        // Set Subject's NameID
        // <NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName">
        //   CN=Alex G. Bell,O=1.22.333.4444,UID=abell
        //</NameID>
        NameID nameId = (NameID) STSUtil.createXMLObject(NameID.DEFAULT_ELEMENT_NAME);
        nameId.setFormat(STSConstants.SUBJECT_NAME_FORMAT);
        nameId.setValue(requestData.getSubjectDN());
        subj.setNameID(nameId);

        // Build subject confirmation.
        SubjectConfirmation subjConf = (SubjectConfirmation) STSUtil
                .createXMLObject(SubjectConfirmation.DEFAULT_ELEMENT_NAME);
        subj.getSubjectConfirmations().add(subjConf);

        // Build SubjectConfirmationData.
        SubjectConfirmationData subjData = (SubjectConfirmationData) STSUtil
                .createXMLObject(SubjectConfirmationData.DEFAULT_ELEMENT_NAME);
        subjConf.setSubjectConfirmationData(subjData); // Attach to SubjectConfirmation.
        subjData.getUnknownAttributes().put(new QName("http://www.w3.org/2001/XMLSchema-instance", "type", "xsi"),
                "saml2:KeyInfoConfirmationDataType");

        // Set the validity period.
        subjData.setNotBefore(createdDate);
        subjData.setNotOnOrAfter(expiresDate);

        // Set confirmation method (and associated data).
        if (authenticationType == STSConstants.AuthenticationType.BINARY_SECURITY_TOKEN) {
            subjConf.setMethod(STSConstants.HOLDER_OF_KEY_SUBJECT_CONFIRMATION_METHOD);
            // Get client certificate and add to SubjectConfirmationData.
            X509Certificate clientCertificate = requestData.getHeaderData().getClientCertificate();
            KeyInfo clientKeyInfo = STSUtil.getKeyInfo(clientCertificate, stsConfig.isEmitSubjectX509Data(),
                    stsConfig.isEmitSubjectPublicKeyValue());
            subjData.getUnknownXMLObjects().add(clientKeyInfo);
        } else {
            subjConf.setMethod(STSConstants.BEARER_SUBJECT_CONFIRMATION_METHOD);
        }

        // Get issuer's "private key" from KeyStore (used to "sign" the Assertion).
        KeyStore keyStore = STSUtil.getKeyStore(stsConfig);
        PrivateKeyEntry pkEntry = STSUtil.getIssuerPrivateKeyEntry(stsConfig, keyStore);
        PrivateKey pk = pkEntry.getPrivateKey();

        // Get issuer's X509Certificate and corresponding KeyInfo.
        X509Certificate issuerCertificate = (X509Certificate) pkEntry.getCertificate();
        KeyInfo issuerKeyInfo = STSUtil.getKeyInfo(issuerCertificate, stsConfig.isEmitIssuerX509Data(),
                stsConfig.isEmitIssuerPublicKeyValue());

        // NO LONGER USED
        //KeyName kn = (KeyName) STSUtil.createXMLObject(KeyName.DEFAULT_ELEMENT_NAME);
        //kn.setValue(stsConfig.getIssuerAlias());
        //keyInfo.getKeyNames().add(kn);

        // Add Attribute statements.
        AttributeStatement as = (AttributeStatement) STSUtil
                .createXMLObject(AttributeStatement.DEFAULT_ELEMENT_NAME);
        SAML2AttributeHandler attributeHandler = new SAML2AttributeHandler();
        List<Attribute> attributes = attributeHandler.handle(requestData);
        as.getAttributes().addAll(attributes);
        assertion.getAttributeStatements().add(as);

        // Set issuer's name (pulled from issuers X509 certificate).
        Issuer issuer = (Issuer) STSUtil.createXMLObject(Issuer.DEFAULT_ELEMENT_NAME);
        issuer.setFormat(STSConstants.SUBJECT_NAME_FORMAT);
        issuer.setValue(issuerCertificate.getIssuerX500Principal().getName());
        assertion.setIssuer(issuer);

        // Get ready to sign the Assertion using the issuer's private key.
        BasicX509Credential credential = new BasicX509Credential();
        credential.setEntityCertificate(issuerCertificate);
        credential.setPrivateKey(pk);
        Signature signature = (Signature) STSUtil.createXMLObject(Signature.DEFAULT_ELEMENT_NAME);
        signature.setSigningCredential(credential);
        signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1);
        signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);

        // FIXME: Do we have a problem here?
        // <ec:InclusiveNamespaces PrefixList="dsig soap #default"
        //xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/>

        // Place the KeyInfo for the issuer in the assertion's Signature.
        signature.setKeyInfo(issuerKeyInfo);
        assertion.setSignature(signature);

        // Fully marshall the Assertion - required in order for the signature to be applied
        Element assertionElement = STSUtil.convertXMLObjectToElement(assertion);

        // Now, "sign" the Assertion using the issuer's private key.
        try {
            Signer.signObject(signature);
        } catch (SignatureException ex) {
            throw new STSException("Unable to sign Assertion: " + ex.getMessage());
        }

        // Convert the response to an OMElement (for subsequent processing).
        OMElement assertionOMElement;
        try {
            assertionOMElement = XMLParser.convertDOMtoOM(assertionElement);
            if (logger.isDebugEnabled()) {
                // Print the assertion to standard output
                AssertionMarshaller marshaller = new AssertionMarshaller();
                Element element = marshaller.marshall(assertion);
                logger.debug(
                        "++++++++++++++++++++++++++++ [formatted] SAML Assertion (BEGIN) ++++++++++++++++++++++++++++");
                logger.debug(XMLHelper.prettyPrintXML(element));
                //logger.debug(assertionOMElement.toString());
                logger.debug(
                        "++++++++++++++++++++++++++++ [formatted] SAML Assertion (END) ++++++++++++++++++++++++++++");
                logger.debug("++++++++++++++++++++++++++++ SAML Assertion (BEGIN) ++++++++++++++++++++++++++++");
                logger.debug(XMLHelper.nodeToString(element));
                //logger.debug(assertionOMElement.toString());
                logger.debug("++++++++++++++++++++++++++++ SAML Assertion (END) ++++++++++++++++++++++++++++");
            }
        } catch (Exception ex) {
            throw new STSException(ex.getMessage());
        }

        // Return a properly formatted WS-Trust response.
        return this.getWSTrustResponse(assertionOMElement, createdDate, expiresDate);
    }

    //
    //  <wst:RequestSecurityTokenResponseCollection xmlns:wst="http://docs.oasis-open.org/ws-sx/ws-trust/200512">
    //          <wst:RequestSecurityTokenResponse>
    //              <wst:TokenType>http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0</wst:TokenType>
    //              <wst:KeySize>256</wst:KeySize>
    //              <wst:RequestedAttachedReference>
    //                  <wsse:SecurityTokenReference xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
    //                      <wsse:Reference URI="#urn:uuid:B5B6F87D35F524847D1307123499963" ValueType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0"/>
    //                  </wsse:SecurityTokenReference>
    //              </wst:RequestedAttachedReference>
    //              <wst:RequestedUnattachedReference>
    //                  <wsse:SecurityTokenReference xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
    //                      <wsse:Reference URI="urn:uuid:B5B6F87D35F524847D1307123499963" ValueType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0"/>
    //                  </wsse:SecurityTokenReference>
    //              </wst:RequestedUnattachedReference>
    //              <wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
    //                  <wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
    //                      <wsa:Address>http://www.vangent.com/X-ServiceProvider-HIEOS</wsa:Address>
    //                  </wsa:EndpointReference>
    //              </wsp:AppliesTo>
    //              <wst:Lifetime>
    //                  <wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2011-06-03T17:51:40.475Z</wsu:Created>
    //                  <wsu:Expires xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2011-06-03T17:56:40.475Z</wsu:Expires>
    //              </wst:Lifetime>
    //              <wst:RequestedSecurityToken>
    //                  <saml:Assertion ID="urn:uuid:B5B6F87D35F524847D1307123499963" IssueInstant="2011-06-03T17:51:39.975Z" Version="2.0" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
    //                      ...
    //                  </saml:Assertion>
    //              </wst:RequestedSecurityToken>
    //              <wst:RequestedProofToken>
    //                  <wst:BinarySecret>aeZRUBMDMzsH1wqLxsXO7W2OMTRE/fNSdQM+XQAeXkc=</wst:BinarySecret>
    //              </wst:RequestedProofToken>
    //          </wst:RequestSecurityTokenResponse>
    //      </wst:RequestSecurityTokenResponseCollection>
    //
    /**
     *
     * @param assertion
     * @param creationDate
     * @param expirationDate
     * @return
     */
    private OMElement getWSTrustResponse(OMElement assertion, DateTime creationDate, DateTime expirationDate) {
        OMFactory omfactory = OMAbstractFactory.getOMFactory();
        OMNamespace wstNs = omfactory.createOMNamespace(STSConstants.WSTRUST_NS, "wst");
        OMNamespace wsuNs = omfactory.createOMNamespace(STSConstants.WSSECURITY_UTILITY_NS, "wsu");
        OMElement rstResponseCollection = omfactory.createOMElement("RequestSecurityTokenResponseCollection",
                wstNs);
        OMElement rstResponse = omfactory.createOMElement("RequestSecurityTokenResponse", wstNs);
        OMElement requestedSecurityToken = omfactory.createOMElement("RequestedSecurityToken", wstNs);
        OMElement lifeTime = omfactory.createOMElement("Lifetime", wstNs);
        OMElement created = omfactory.createOMElement("Created", wsuNs);
        OMElement expires = omfactory.createOMElement("Expires", wsuNs);
        created.setText(creationDate.toString());
        expires.setText(expirationDate.toString());
        lifeTime.addChild(created);
        lifeTime.addChild(expires);

        OMElement tokenType = omfactory.createOMElement("TokenType", wstNs);
        tokenType.setText(STSConstants.SAML2_TOKEN_TYPE);

        // Wire things up in proper order.
        rstResponseCollection.addChild(rstResponse);
        rstResponse.addChild(tokenType);
        rstResponse.addChild(lifeTime);
        rstResponse.addChild(requestedSecurityToken);
        requestedSecurityToken.addChild(assertion);
        return rstResponseCollection;
    }

    /**
     *
     * @param authenticationType
     * @return
     * @throws STSException
     */
    private AuthnStatement getAuthnStatement(STSConstants.AuthenticationType authenticationType)
            throws STSException {
        // Construct AuthnStatement.
        AuthnStatement authStmt = (AuthnStatement) STSUtil.createXMLObject(AuthnStatement.DEFAULT_ELEMENT_NAME);

        // Set the Authentication instant.
        authStmt.setAuthnInstant(new DateTime());
        AuthnContext authContext = (AuthnContext) STSUtil.createXMLObject(AuthnContext.DEFAULT_ELEMENT_NAME);
        AuthnContextClassRef authCtxClassRef = (AuthnContextClassRef) STSUtil
                .createXMLObject(AuthnContextClassRef.DEFAULT_ELEMENT_NAME);
        if (authenticationType == STSConstants.AuthenticationType.BINARY_SECURITY_TOKEN) {
            authCtxClassRef.setAuthnContextClassRef(AuthnContext.X509_AUTHN_CTX);
        } else {
            // FIXME: May need: urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
            authCtxClassRef.setAuthnContextClassRef(AuthnContext.PASSWORD_AUTHN_CTX);
        }

        // FIXME: Put in code to require https @ a minimum.
        // authCtxClassRef.setAuthnContextClassRef(AuthnContext.X509_AUTHN_CTX);

        authContext.setAuthnContextClassRef(authCtxClassRef);
        authStmt.setAuthnContext(authContext);
        return authStmt;
    }
}