com.wandrell.example.swss.test.util.factory.SecureSoapMessages.java Source code

Java tutorial

Introduction

Here is the source code for com.wandrell.example.swss.test.util.factory.SecureSoapMessages.java

Source

/**
 * The MIT License (MIT)
 * <p>
 * Copyright (c) 2015 the original author or authors.
 * <p>
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * <p>
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * <p>
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package com.wandrell.example.swss.test.util.factory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TimeZone;

import javax.xml.soap.MessageFactory;
import javax.xml.soap.MimeHeaders;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.tools.ant.util.Base64Converter;
import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.transforms.Transforms;
import org.apache.xml.security.utils.Constants;
import org.apache.xml.security.utils.XMLUtils;
import org.springframework.core.io.ClassPathResource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.wandrell.example.swss.test.util.SoapMessageUtils;

import freemarker.template.Configuration;
import freemarker.template.Template;

/**
 * Factory method for secured SOAP messages.
 * <p>
 * These can be contained in a {@code SOAPMessage}, or be the input of an
 * {@code InputStream}. In the second case the {@code InputStream} will point to
 * a string containing the full message.
 *
 * @author Bernardo Martnez Garrido
 */
public final class SecureSoapMessages {

    /**
     * Creates a SOAP message with a digested password, username and nonce.
     * <p>
     * The nonce will be generated during the securing process.
     * <p>
     * A freemarker template should be provided, it will be used to generate the
     * final message from the received parameters.
     *
     * @param path
     *            path to the freemarker template
     * @param user
     *            user to include in the message
     * @param password
     *            password to include in the message
     * @return a digested password {@code SOAPMessage}
     * @return a SOAP message with a digested password, username and nonce
     * @throws Exception
     *             if any error occurs during the message creation
     */
    public static final SOAPMessage getDigestedPasswordMessage(final String path, final String user,
            final String password) throws Exception {
        return MessageFactory.newInstance().createMessage(new MimeHeaders(),
                getDigestedPasswordStream(path, user, password));
    }

    /**
     * Creates a SOAP message with a digested password, username and nonce.
     * <p>
     * The nonce will be generated during the securing process.
     * <p>
     * A freemarker template should be provided, it will be used to generate the
     * final message from the received parameters.
     * 
     * @param path
     *            path to the freemarker template
     * @param user
     *            user to include in the message
     * @param password
     *            password to include in the message
     * @return a SOAP message with a digested password, username and nonce
     * @throws Exception
     *             if any error occurs during the message creation
     */
    public static final InputStream getDigestedPasswordStream(final String path, final String user,
            final String password) throws Exception {
        return new ByteArrayInputStream(getDigestedPasswordMessageContent(path, user, password).getBytes("UTF-8"));
    }

    /**
     * Creates a SOAP message with a plain password and username.
     * <p>
     * A freemarker template should be provided, it will be used to generate the
     * final message from the received parameters.
     * 
     * @param path
     *            path to the freemarker template
     * @param user
     *            user to include in the message
     * @param password
     *            password to include in the message
     * @return a SOAP message with a plain password and username
     * @throws Exception
     *             if any error occurs during the message creation
     */
    public static final SOAPMessage getPlainPasswordMessage(final String path, final String user,
            final String password) throws Exception {
        return MessageFactory.newInstance().createMessage(new MimeHeaders(),
                getPlainPasswordStream(path, user, password));
    }

    /**
     * Creates a SOAP message with a plain password and username.
     * <p>
     * A freemarker template should be provided, it will be used to generate the
     * final message from the received parameters.
     * 
     * @param path
     *            path to the freemarker template
     * @param user
     *            user to include in the message
     * @param password
     *            password to include in the message
     * @return a SOAP message with a plain password and username
     * @throws Exception
     *             if any error occurs during the message creation
     */
    public static final InputStream getPlainPasswordStream(final String path, final String user,
            final String password) throws Exception {
        return new ByteArrayInputStream(getPlainPasswordMessageContent(path, user, password).getBytes("UTF-8"));
    }

    /**
     * Creates a SOAP message with a signature.
     * <p>
     * A valid SOAP message is required, this will be the message to be signed.
     * 
     * @param pathBase
     *            path to the SOAP message to sign
     * @param privateKeyAlias
     *            alias for the private key
     * @param privateKeyPass
     *            password for the private key
     * @param certificateAlias
     *            alias for the certificate
     * @param keystore
     *            key store for the signing
     * @return a singed SOAP message
     * @throws Exception
     *             if any error occurs during the message creation
     */
    public static final SOAPMessage getSignedMessage(final String pathBase, final String privateKeyAlias,
            final String privateKeyPass, final String certificateAlias, final KeyStore keystore) throws Exception {
        Element root = null;
        String BaseURI = new ClassPathResource(pathBase).getURI().toString();
        SOAPMessage soapMessage;
        Base64Converter base64 = new Base64Converter();
        String token;
        Node binaryToken;
        X509Certificate cert;
        PrivateKey privateKey;
        XMLSignature sig;

        soapMessage = getMessageToSign(pathBase);

        // get the private key used to sign, from the keystore
        privateKey = (PrivateKey) keystore.getKey(privateKeyAlias, privateKeyPass.toCharArray());
        cert = (X509Certificate) keystore.getCertificate(certificateAlias);

        // create basic structure of signature
        Document doc = toDocument(soapMessage);

        org.apache.xml.security.Init.init();

        sig = getSignature(doc, BaseURI, cert, privateKey);

        // optional, but better
        root = doc.getDocumentElement();
        root.normalize();
        root.getElementsByTagName("wsse:Security").item(0).appendChild(sig.getElement());

        token = base64.encode(cert.getEncoded());

        binaryToken = root.getElementsByTagName("wsse:BinarySecurityToken").item(0);
        binaryToken.setTextContent(token);

        // write signature to file
        XMLUtils.outputDOMc14nWithComments(doc, System.out);

        return toMessage(doc);
    }

    /**
     * Creates a SOAP message with a signature.
     * <p>
     * A valid SOAP message is required, this will be the message to be signed.
     * 
     * @param pathBase
     *            path to the SOAP message to sign
     * @param privateKeyAlias
     *            alias for the private key
     * @param privateKeyPass
     *            password for the private key
     * @param certificateAlias
     *            alias for the certificate
     * @param keystore
     *            key store for the signing
     * @return a singed SOAP message
     * @throws Exception
     *             if any error occurs during the message creation
     */
    public static final InputStream getSignedStream(final String pathBase, final String privateKeyAlias,
            final String privateKeyPass, final String certificateAlias, final KeyStore keystore) throws Exception {
        SOAPMessage msg = SecureSoapMessages.getSignedMessage(pathBase, privateKeyAlias, privateKeyPass,
                certificateAlias, keystore);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        msg.writeTo(out);
        String strMsg = new String(out.toByteArray());
        return new ByteArrayInputStream(strMsg.getBytes());
    }

    /**
     * Generates the digest value for the SOAP secure header.
     * <p>
     * This is a codified password, with the help of the date and nonce values.
     * Both of these values should be found on the SOAP secure header.
     *
     * @param password
     *            password to digest
     * @param date
     *            date used on the SOAP header
     * @param nonce
     *            nonce used on the SOAP header
     * @return the digested password
     * @throws UnsupportedEncodingException
     *             if the UTF-8 encoding is not supported
     */
    private static final String generateDigest(final String password, final String date, final String nonce)
            throws UnsupportedEncodingException {
        final ByteBuffer buf; // Buffers storing the data to digest
        byte[] toHash; // Bytes to generate the hash

        // Fills buffer with data to digest
        buf = ByteBuffer.allocate(1000);
        buf.put(Base64.decodeBase64(nonce));
        buf.put(date.getBytes("UTF-8"));
        buf.put(password.getBytes("UTF-8"));

        // Initializes hash bytes to the correct size
        toHash = new byte[buf.position()];
        buf.rewind();

        // Copies bytes from the buffer to the hash bytes
        buf.get(toHash);

        return Base64.encodeBase64String(DigestUtils.sha1(toHash));
    }

    /**
     * Generates the current date in the format expected by the SOAP message.
     *
     * @return the current date
     */
    private static final String getCurrentDate() {
        final DateFormat format; // Format to apply

        // Zulu time format
        format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        format.setTimeZone(TimeZone.getTimeZone("UTC"));

        return format.format(new Date());
    }

    /**
     * Generates the text content for the digested password SOAP message.
     * <p>
     * This will be created from a freemarker template.
     *
     * @param path
     *            path to the freemarker template
     * @param user
     *            username to use
     * @param password
     *            password to use
     * @return the text content for the digested password message
     * @throws Exception
     *             if any error occurs during the message creation
     */
    private static final String getDigestedPasswordMessageContent(final String path, final String user,
            final String password) throws Exception {
        final String nonce; // Nonce for the message
        final String date; // Current date
        final String digest; // Digested password
        final Template template; // Freemarker template
        final Map<String, Object> data; // Data for the template
        final ByteArrayOutputStream out; // Steam with the message

        // Generates security data
        nonce = getNonce();
        date = getCurrentDate();
        digest = generateDigest(password, date, nonce);

        // Prepares the data for the template
        data = new LinkedHashMap<String, Object>();
        data.put("user", user);
        data.put("password", password);
        data.put("nonce", nonce);
        data.put("date", date);
        data.put("digest", digest);

        // Processes the template to the output
        out = new ByteArrayOutputStream();
        template = new Configuration(Configuration.VERSION_2_3_0).getTemplate(path);
        template.process(data, new OutputStreamWriter(out));

        return new String(out.toByteArray());
    }

    private static final SOAPMessage getMessageToSign(final String pathBase) throws SOAPException, IOException {
        final SOAPMessage soapMessage;
        final SOAPPart soapPart;
        final SOAPEnvelope soapEnvelope;
        final SOAPHeader soapHeader;
        final SOAPHeaderElement secElement;
        final SOAPElement binaryTokenElement;

        soapMessage = SoapMessageUtils.getMessage(pathBase);
        soapPart = soapMessage.getSOAPPart();
        soapEnvelope = soapPart.getEnvelope();
        soapHeader = soapEnvelope.getHeader();

        secElement = soapHeader.addHeaderElement(soapEnvelope.createName("Security", "wsse",
                "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"));
        binaryTokenElement = secElement.addChildElement(soapEnvelope.createName("BinarySecurityToken", "wsse",
                "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"));
        binaryTokenElement.setAttribute("EncodingType",
                "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
        binaryTokenElement.setAttribute("ValueType",
                "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3");

        return soapMessage;
    }

    /**
     * Generates a nonce value for the SOAP secure header.
     *
     * @return the nonce value
     * @throws Exception
     *             if any error occurs while generating the nonce
     */
    private static final String getNonce() throws Exception {
        final SecureRandom random; // Random value generator
        final byte[] nonceBytes; // Bytes to generate the nonce

        random = SecureRandom.getInstance("SHA1PRNG");
        random.setSeed(System.currentTimeMillis());
        nonceBytes = new byte[16];
        random.nextBytes(nonceBytes);

        return new String(Base64.encodeBase64(nonceBytes), "UTF-8");
    }

    /**
     * Generates the text content for the plain password SOAP message.
     * <p>
     * This will be created from a freemarker template.
     *
     * @param path
     *            path to the freemarker template
     * @param user
     *            username to use
     * @param password
     *            password to use
     * @return the text content for the plain passworde message
     * @throws Exception
     *             if any error occurs during the message creation
     */
    private static final String getPlainPasswordMessageContent(final String path, final String user,
            final String password) throws Exception {
        final Template template; // Freemarker template
        final Map<String, Object> data; // Data for the template
        final ByteArrayOutputStream out; // Steam with the message

        // Prepares the data for the template
        data = new LinkedHashMap<String, Object>();
        data.put("user", user);
        data.put("password", password);

        // Processes the template to the output
        out = new ByteArrayOutputStream();
        template = new Configuration(Configuration.VERSION_2_3_0).getTemplate(path);
        template.process(data, new OutputStreamWriter(out));

        return new String(out.toByteArray());
    }

    private static final XMLSignature getSignature(final Document doc, final String BaseURI,
            final X509Certificate cert, final PrivateKey privateKey) throws XMLSecurityException {
        final XMLSignature sig;

        sig = new XMLSignature(doc, BaseURI, XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1);

        Transforms transforms = new Transforms(doc);
        transforms.addTransform(Transforms.TRANSFORM_C14N_OMIT_COMMENTS);
        // Sign the content of SOAP Envelope
        sig.addDocument("", transforms, Constants.ALGO_ID_DIGEST_SHA1);

        sig.addKeyInfo(cert);
        sig.addKeyInfo(cert.getPublicKey());
        sig.sign(privateKey);

        return sig;
    }

    private static final Document toDocument(SOAPMessage soapMsg)
            throws TransformerConfigurationException, TransformerException, SOAPException, IOException {
        Source src = soapMsg.getSOAPPart().getContent();
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        DOMResult result = new DOMResult();
        transformer.transform(src, result);
        return (Document) result.getNode();
    }

    private static final SOAPMessage toMessage(Document jdomDocument) throws IOException, SOAPException {
        SOAPMessage message = MessageFactory.newInstance().createMessage();
        SOAPPart sp = message.getSOAPPart();
        sp.setContent(new DOMSource(jdomDocument.getFirstChild()));

        return message;
    }

    /**
     * Private constructor to avoid initialization.
     */
    private SecureSoapMessages() {
        super();
    }

}