be.fedict.hsm.ws.impl.WSSecuritySOAPHandler.java Source code

Java tutorial

Introduction

Here is the source code for be.fedict.hsm.ws.impl.WSSecuritySOAPHandler.java

Source

/*
 * HSM Proxy Project.
 * Copyright (C) 2013 FedICT.
 * Copyright (C) 2011-2012 AGIV.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version
 * 3.0 as published by the Free Software Foundation.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, see 
 * http://www.gnu.org/licenses/.
 */

package be.fedict.hsm.ws.impl;

import java.security.cert.X509Certificate;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.ejb.EJB;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.ws.ProtocolException;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.WSDataRef;
import org.apache.ws.security.WSSConfig;
import org.apache.ws.security.WSSecurityEngine;
import org.apache.ws.security.WSSecurityEngineResult;
import org.apache.ws.security.WSSecurityException;
import org.apache.ws.security.components.crypto.Crypto;
import org.apache.ws.security.message.token.BinarySecurity;
import org.apache.ws.security.message.token.Timestamp;
import org.apache.ws.security.util.WSSecurityUtil;

import be.fedict.hsm.model.security.SecurityAuditGeneratorBean;
import be.fedict.hsm.model.security.SecurityFunction;

/**
 * WS-Security verification SOAP handler. This SOAP handler checks whether a
 * WS-Security header is present. The WS-Security header should include a
 * timestamp and a signature over both the timestamp and the SOAP Body. The
 * received X509 certificate acts as the application credential.
 * <p/>
 * Even if we later on decide to use WS-SecurityPolicy via Apache CXF, we still
 * want to keep these additional checks in place.
 * 
 * @author Frank Cornelis
 * 
 */
@SecurityFunction("SF.I&A.1")
public class WSSecuritySOAPHandler implements SOAPHandler<SOAPMessageContext> {

    private static final Log LOG = LogFactory.getLog(WSSecuritySOAPHandler.class);

    private static final String X509_ATTRIBUTE = WSSecuritySOAPHandler.class.getName() + ".x509";

    private static final String WSU_NAMESPACE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";

    @EJB
    private SecurityAuditGeneratorBean securityAuditGeneratorBean;

    @Override
    public boolean handleMessage(SOAPMessageContext context) {
        LOG.debug("handleMessage");
        Boolean outboundProperty = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
        if (false == outboundProperty.booleanValue()) {
            try {
                handleInboundMessage(context);
            } catch (Exception e) {
                throw new ProtocolException(e);
            }
        }
        return true;
    }

    private void handleInboundMessage(SOAPMessageContext context) throws WSSecurityException, SOAPException {
        LOG.debug("checking WS-Security header");
        SOAPMessage soapMessage = context.getMessage();
        SOAPPart soapPart = soapMessage.getSOAPPart();

        WSSecurityEngine secEngine = new WSSecurityEngine();
        Crypto crypto = new WSSecurityCrypto();
        WSSConfig wssConfig = new WSSConfig();
        wssConfig.setWsiBSPCompliant(true);
        secEngine.setWssConfig(wssConfig);
        List<WSSecurityEngineResult> results = secEngine.processSecurityHeader(soapPart, null, null, crypto);
        if (null == results) {
            this.securityAuditGeneratorBean.webServiceAuthenticationError();
            throw new SecurityException("no WS-Security results");
        }

        WSSecurityEngineResult timeStampActionResult = WSSecurityUtil.fetchActionResult(results, WSConstants.TS);
        if (null == timeStampActionResult) {
            this.securityAuditGeneratorBean.webServiceAuthenticationError();
            throw new SecurityException("no WS-Security timestamp result");
        }

        Timestamp receivedTimestamp = (Timestamp) timeStampActionResult.get(WSSecurityEngineResult.TAG_TIMESTAMP);
        if (null == receivedTimestamp) {
            this.securityAuditGeneratorBean.webServiceAuthenticationError();
            throw new SecurityException("no WS-Security timestamp");
        }

        LOG.debug("WS-Security timestamp created: " + receivedTimestamp.getCreated());
        LOG.debug("WS-Security timestamp expires: " + receivedTimestamp.getExpires());
        String timeStampIdRef = "#" + receivedTimestamp.getID();

        WSSecurityEngineResult bstActionResult = WSSecurityUtil.fetchActionResult(results, WSConstants.BST);
        if (null == bstActionResult) {
            this.securityAuditGeneratorBean.webServiceAuthenticationError();
            throw new SecurityException("no WS-Security BinarySecurityToken");
        }
        BinarySecurity binarySecurityToken = (BinarySecurity) bstActionResult
                .get(WSSecurityEngineResult.TAG_BINARY_SECURITY_TOKEN);

        WSSecurityEngineResult signActionResult = WSSecurityUtil.fetchActionResult(results, WSConstants.SIGN);
        if (null == signActionResult) {
            this.securityAuditGeneratorBean.webServiceAuthenticationError();
            throw new SecurityException("no valid XML signature");
        }
        String signatureMethod = (String) signActionResult.get(WSSecurityEngineResult.TAG_SIGNATURE_METHOD);
        LOG.debug("signature method: " + signatureMethod);
        if (false == "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256".equals(signatureMethod)) {
            this.securityAuditGeneratorBean.webServiceAuthenticationError();
            throw new SecurityException("signature algo should be RSA-SHA256");
        }
        X509Certificate certificate = (X509Certificate) signActionResult
                .get(WSSecurityEngineResult.TAG_X509_CERTIFICATE);
        LOG.debug("certificate subject: " + certificate.getSubjectX500Principal());
        List<WSDataRef> wsDataRefs = (List<WSDataRef>) signActionResult
                .get(WSSecurityEngineResult.TAG_DATA_REF_URIS);

        SOAPEnvelope soapEnvelope = soapPart.getEnvelope();
        SOAPBody soapBody = soapEnvelope.getBody();
        String bodyIdRef = "#" + soapBody.getAttributeNS(WSU_NAMESPACE, "Id");
        String bstIdRef = "#" + binarySecurityToken.getID();

        boolean timestampDigested = false;
        boolean bodyDigested = false;
        boolean tokenDigested = false;
        for (WSDataRef wsDataRef : wsDataRefs) {
            String wsuId = wsDataRef.getWsuId();
            LOG.debug("signed wsu:Id: " + wsuId);
            LOG.debug("digest algorithm: " + wsDataRef.getDigestAlgorithm());
            if (false == "http://www.w3.org/2001/04/xmlenc#sha256".equals(wsDataRef.getDigestAlgorithm())) {
                this.securityAuditGeneratorBean.webServiceAuthenticationError(certificate);
                throw new SecurityException("digest algorithm should be SHA256");
            }
            if (timeStampIdRef.equals(wsuId)) {
                timestampDigested = true;
            } else if (bodyIdRef.equals(wsuId)) {
                bodyDigested = true;
            } else if (bstIdRef.equals(wsuId)) {
                tokenDigested = true;
            }
        }
        if (false == timestampDigested) {
            this.securityAuditGeneratorBean.webServiceAuthenticationError(certificate);
            throw new SecurityException("timestamp not digested");
        }
        if (false == bodyDigested) {
            this.securityAuditGeneratorBean.webServiceAuthenticationError(certificate);
            throw new SecurityException("SOAP Body not digested");
        }
        if (false == tokenDigested) {
            this.securityAuditGeneratorBean.webServiceAuthenticationError(certificate);
            throw new SecurityException("BinarySecurityToken not digested");
        }

        context.put(X509_ATTRIBUTE, certificate);
    }

    @Override
    public boolean handleFault(SOAPMessageContext context) {
        return true;
    }

    @Override
    public void close(MessageContext context) {
    }

    @Override
    public Set<QName> getHeaders() {
        Set<QName> headers = new HashSet<QName>();
        headers.add(new QName(WSConstants.WSSE_NS, WSConstants.WSSE_LN));
        return headers;
    }

    public static X509Certificate getAuthenticatedCertificate(SOAPMessageContext context) {
        return (X509Certificate) context.get(X509_ATTRIBUTE);
    }
}