test.unit.be.agiv.security.handler.WSSecurityHandlerTest.java Source code

Java tutorial

Introduction

Here is the source code for test.unit.be.agiv.security.handler.WSSecurityHandlerTest.java

Source

/*
 * AGIV Java Security Project.
 * 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 test.unit.be.agiv.security.handler;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.math.BigInteger;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.RSAKeyGenParameterSpec;
import java.util.Properties;
import java.util.UUID;

import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPConstants;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.ws.ProtocolException;
import javax.xml.ws.handler.soap.SOAPMessageContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ws.security.message.WSSecHeader;
import org.apache.ws.security.message.WSSecTimestamp;
import org.apache.ws.security.util.WSSecurityUtil;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.utils.Constants;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.util.PrivateKeyFactory;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
import org.easymock.EasyMock;
import org.joda.time.DateTime;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import test.unit.be.agiv.security.client.TestUtils;
import be.agiv.security.handler.WSAddressingHandler;
import be.agiv.security.handler.WSSecurityHandler;

import com.sun.org.apache.xpath.internal.XPathAPI;

public class WSSecurityHandlerTest {

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

    private WSSecurityHandler testedInstance;

    @BeforeClass
    public static void registerBouncyCastle() throws Exception {
        Security.addProvider(new BouncyCastleProvider());
    }

    @Before
    public void setUp() throws Exception {
        this.testedInstance = new WSSecurityHandler();
    }

    @Test
    public void testVerifyTimestampExpired() throws Exception {
        // setup
        SOAPMessageContext mockContext = EasyMock.createMock(SOAPMessageContext.class);

        EasyMock.expect(mockContext.get("javax.xml.ws.handler.message.outbound")).andStubReturn(Boolean.FALSE);

        InputStream requestInputStream = WSSecurityHandlerTest.class.getResourceAsStream("/ip-sts-response.xml");
        SOAPMessage soapMessage = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL).createMessage(null,
                requestInputStream);
        EasyMock.expect(mockContext.getMessage()).andStubReturn(soapMessage);

        // prepare
        EasyMock.replay(mockContext);

        // operate
        try {
            this.testedInstance.handleMessage(mockContext);
            fail();
        } catch (ProtocolException e) {
            // verify
            EasyMock.verify(mockContext);
        }
    }

    @Test
    public void testVerifyTimestamp() throws Exception {
        // setup
        SOAPMessageContext mockContext = EasyMock.createMock(SOAPMessageContext.class);

        EasyMock.expect(mockContext.get("javax.xml.ws.handler.message.outbound")).andStubReturn(Boolean.FALSE);

        SOAPMessage soapMessage = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL).createMessage();

        SOAPBody soapBody = soapMessage.getSOAPBody();
        soapBody.addBodyElement(new QName("test"));

        SOAPPart soapPart = soapMessage.getSOAPPart();
        WSSecHeader secHeader = new WSSecHeader();
        secHeader.insertSecurityHeader(soapPart);
        WSSecTimestamp timestamp = new WSSecTimestamp();
        timestamp.build(soapPart, secHeader);

        LOG.debug("SOAP message: " + toString(soapMessage.getSOAPPart()));
        EasyMock.expect(mockContext.getMessage()).andStubReturn(soapMessage);

        // prepare
        EasyMock.replay(mockContext);

        // operate
        boolean result = this.testedInstance.handleMessage(mockContext);

        // verify
        EasyMock.verify(mockContext);
        assertTrue(result);
    }

    @Test
    public void testVerifyTimestampMissing() throws Exception {
        // setup
        SOAPMessageContext mockContext = EasyMock.createMock(SOAPMessageContext.class);

        EasyMock.expect(mockContext.get("javax.xml.ws.handler.message.outbound")).andStubReturn(Boolean.FALSE);

        SOAPMessage soapMessage = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL).createMessage();

        SOAPBody soapBody = soapMessage.getSOAPBody();
        soapBody.addBodyElement(new QName("test"));

        LOG.debug("SOAP message: " + toString(soapMessage.getSOAPPart()));
        EasyMock.expect(mockContext.getMessage()).andStubReturn(soapMessage);

        // prepare
        EasyMock.replay(mockContext);

        // operate
        try {
            this.testedInstance.handleMessage(mockContext);
            fail();
        } catch (ProtocolException e) {
            // verify
            EasyMock.verify(mockContext);
        }
    }

    @Test
    public void testSignature() throws Exception {
        // setup
        SOAPMessageContext mockContext = EasyMock.createMock(SOAPMessageContext.class);

        EasyMock.expect(mockContext.get("javax.xml.ws.handler.message.outbound")).andStubReturn(Boolean.TRUE);

        byte[] secret = new byte[256 / 8];
        SecureRandom secureRandom = new SecureRandom();
        secureRandom.nextBytes(secret);

        String tokenIdentifier = "#saml-token-test";
        this.testedInstance.setKey(secret, tokenIdentifier, null, false);

        InputStream requestInputStream = WSSecurityHandlerTest.class
                .getResourceAsStream("/r-sts-request-before-signing.xml");
        SOAPMessage soapMessage = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL).createMessage(null,
                requestInputStream);
        EasyMock.expect(mockContext.getMessage()).andStubReturn(soapMessage);

        // prepare
        EasyMock.replay(mockContext);

        // operate
        boolean result = this.testedInstance.handleMessage(mockContext);

        // verify
        EasyMock.verify(mockContext);
        assertTrue(result);

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        soapMessage.writeTo(outputStream);
        LOG.debug("SOAP message: " + new String(outputStream.toByteArray()));

        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setNamespaceAware(true);
        DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(outputStream.toByteArray());
        Document resultDocument = documentBuilder.parse(byteArrayInputStream);
        TestUtils.markAllIdAttributesAsId(resultDocument);

        NodeList signatureNodeList = resultDocument.getElementsByTagNameNS(Constants.SignatureSpecNS, "Signature");
        assertEquals(1, signatureNodeList.getLength());
        Element signatureElement = (Element) signatureNodeList.item(0);

        XMLSignature xmlSignature = new XMLSignature(signatureElement, null);
        Key key = WSSecurityUtil.prepareSecretKey(SignatureMethod.HMAC_SHA1, secret);
        boolean signatureResult = xmlSignature.checkSignatureValue(key);
        assertTrue(signatureResult);

        LOG.debug("signed SOAP: " + toString(resultDocument));
    }

    @Test
    public void testCertificateSignature() throws Exception {
        // setup
        SOAPMessageContext mockContext = EasyMock.createMock(SOAPMessageContext.class);

        EasyMock.expect(mockContext.get("javax.xml.ws.handler.message.outbound")).andStubReturn(Boolean.TRUE);

        SOAPMessage soapMessage = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL).createMessage(null,
                new ByteArrayInputStream(
                        ("<soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:wsa=\"http://www.w3.org/2005/08/addressing\" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">"
                                + "<soap:Header>"
                                + "<wsa:To soap:mustUnderstand=\"1\" wsu:Id=\"toId\">destination</wsa:To>"
                                + "</soap:Header>" + "<soap:Body>test</soap:Body>" + "</soap:Envelope>")
                                        .getBytes()));
        EasyMock.expect(mockContext.getMessage()).andStubReturn(soapMessage);

        EasyMock.expect(mockContext.get(WSAddressingHandler.class.getName() + ".toId")).andStubReturn("toId");

        KeyPair keyPair = generateKeyPair();
        PrivateKey privateKey = keyPair.getPrivate();

        X509Certificate certificate = generateSelfSignedCertificate(keyPair);
        this.testedInstance.setCredentials(privateKey, certificate);

        // prepare
        EasyMock.replay(mockContext);

        // operate
        boolean result = this.testedInstance.handleMessage(mockContext);

        // verify
        EasyMock.verify(mockContext);
        assertTrue(result);

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        soapMessage.writeTo(outputStream);
        LOG.debug("SOAP message: " + new String(outputStream.toByteArray()));

        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setNamespaceAware(true);
        DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(outputStream.toByteArray());
        Document resultDocument = documentBuilder.parse(byteArrayInputStream);
        TestUtils.markAllIdAttributesAsId(resultDocument);

        NodeList signatureNodeList = resultDocument.getElementsByTagNameNS(Constants.SignatureSpecNS, "Signature");
        assertEquals(1, signatureNodeList.getLength());
        Element signatureElement = (Element) signatureNodeList.item(0);

        XMLSignature xmlSignature = new XMLSignature(signatureElement, null);
        boolean signatureResult = xmlSignature.checkSignatureValue(certificate);
        assertTrue(signatureResult);

        LOG.debug("signed SOAP: " + toString(resultDocument));
    }

    @Test
    public void testUsernameToken() throws Exception {
        // setup
        WSSecurityHandler testedInstance = new WSSecurityHandler();

        SOAPMessageContext mockContext = EasyMock.createMock(SOAPMessageContext.class);

        EasyMock.expect(mockContext.get("javax.xml.ws.handler.message.outbound")).andStubReturn(Boolean.TRUE);
        String testUsername = "username-" + UUID.randomUUID().toString();

        testedInstance.setCredentials(testUsername, "password");

        SOAPMessage soapMessage = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL).createMessage(null,
                new ByteArrayInputStream(
                        "<Envelope xmlns=\"http://schemas.xmlsoap.org/soap/envelope/\"><Body>test</Body></Envelope>"
                                .getBytes()));

        LOG.debug("SOAP message: " + toString(soapMessage.getSOAPPart()));
        EasyMock.expect(mockContext.getMessage()).andStubReturn(soapMessage);

        // prepare
        EasyMock.replay(mockContext);

        // operate
        testedInstance.handleMessage(mockContext);

        // verify
        EasyMock.verify(mockContext);
        LOG.debug("SOAP message after handleMessage: " + toString(soapMessage.getSOAPPart()));

        Element nsElement = getNSElement(soapMessage.getSOAPPart());
        String resultUsername = XPathAPI.selectSingleNode(soapMessage.getSOAPPart(),
                "soap:Envelope/soap:Header/wsse:Security/wsse:UsernameToken/wsse:Username/text()", nsElement)
                .getNodeValue();
        assertEquals(testUsername, resultUsername);
    }

    private Element getNSElement(Document document) {
        Element nsElement = document.createElement("ns");
        nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:soap",
                "http://schemas.xmlsoap.org/soap/envelope/");
        nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:soap12",
                "http://www.w3.org/2003/05/soap-envelope");
        nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:trust",
                "http://docs.oasis-open.org/ws-sx/ws-trust/200512");
        nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#");
        nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:wsse",
                "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
        nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:wsu",
                "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
        return nsElement;
    }

    private KeyPair generateKeyPair() throws Exception {
        return generateKeyPair(1024);
    }

    private KeyPair generateKeyPair(int keySize) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        SecureRandom random = new SecureRandom();
        keyPairGenerator.initialize(new RSAKeyGenParameterSpec(keySize, RSAKeyGenParameterSpec.F4), random);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        return keyPair;
    }

    private X509Certificate generateSelfSignedCertificate(KeyPair keyPair) throws Exception {
        X500Name issuer = new X500Name("CN=Test");
        X500Name subject = issuer;

        SecureRandom secureRandom = new SecureRandom();
        byte[] serialValue = new byte[8];
        secureRandom.nextBytes(serialValue);
        BigInteger serial = new BigInteger(serialValue);

        DateTime notBefore = new DateTime();
        DateTime notAfter = notBefore.plusMonths(1);

        SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());

        X509v3CertificateBuilder x509v3CertificateBuilder = new X509v3CertificateBuilder(issuer, serial,
                notBefore.toDate(), notAfter.toDate(), subject, publicKeyInfo);

        AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
        AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
        AsymmetricKeyParameter asymmetricKeyParameter = PrivateKeyFactory
                .createKey(keyPair.getPrivate().getEncoded());
        ContentSigner contentSigner = new BcRSAContentSignerBuilder(sigAlgId, digAlgId)
                .build(asymmetricKeyParameter);
        X509CertificateHolder x509CertificateHolder = x509v3CertificateBuilder.build(contentSigner);

        byte[] encodedCertificate = x509CertificateHolder.getEncoded();

        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
        X509Certificate certificate = (X509Certificate) certificateFactory
                .generateCertificate(new ByteArrayInputStream(encodedCertificate));
        return certificate;
    }

    @Test
    public void testWSSecurityWithoutInitialHeader() throws Exception {
        // setup
        SOAPMessageContext mockContext = EasyMock.createMock(SOAPMessageContext.class);

        EasyMock.expect(mockContext.get("javax.xml.ws.handler.message.outbound")).andStubReturn(Boolean.TRUE);
        EasyMock.expect(mockContext.get("be.agiv.security.handler.WSSecurityHandler.token")).andStubReturn(null);
        EasyMock.expect(mockContext.get("be.agiv.security.handler.WSSecurityHandler.username"))
                .andStubReturn("username");
        EasyMock.expect(mockContext.get("be.agiv.security.handler.WSSecurityHandler.password"))
                .andStubReturn("password");
        EasyMock.expect(mockContext.get("be.agiv.security.handler.WSSecurityHandler.key")).andStubReturn(null);

        SOAPMessage soapMessage = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL).createMessage(null,
                new ByteArrayInputStream(
                        "<Envelope xmlns=\"http://www.w3.org/2003/05/soap-envelope\"><Body>test</Body></Envelope>"
                                .getBytes()));

        LOG.debug("SOAP message: " + toString(soapMessage.getSOAPPart()));
        EasyMock.expect(mockContext.getMessage()).andStubReturn(soapMessage);

        EasyMock.expect(mockContext.get(WSSecurityHandler.class.getName() + ".certificate")).andStubReturn(null);

        // prepare
        EasyMock.replay(mockContext);

        // operate
        this.testedInstance.handleMessage(mockContext);

        // verify
        EasyMock.verify(mockContext);
    }

    private String toString(Document document) throws TransformerException {
        StringWriter stringWriter = new StringWriter();
        StreamResult streamResult = new StreamResult(stringWriter);
        Properties properties = new Properties();
        properties.put(OutputKeys.METHOD, "html");
        properties.put(OutputKeys.INDENT, "5");
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = null;
        transformer = transformerFactory.newTransformer();
        transformer.setOutputProperties(properties);
        transformer.transform(new DOMSource(document), streamResult);
        return stringWriter.toString();
    }
}