test.unit.be.fedict.eid.dss.spi.utils.XAdESValidationTest.java Source code

Java tutorial

Introduction

Here is the source code for test.unit.be.fedict.eid.dss.spi.utils.XAdESValidationTest.java

Source

/*
 * eID Digital Signature Service Project.
 * Copyright (C) 2010 FedICT.
 *
 * 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.fedict.eid.dss.spi.utils;

import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
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.OutputStream;
import java.security.KeyPair;
import java.security.Security;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import javax.crypto.Cipher;
import javax.xml.crypto.KeySelector;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xml.security.utils.Constants;
import org.apache.xpath.XPathAPI;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.ocsp.OCSPResp;
import org.bouncycastle.tsp.TimeStampToken;
import org.easymock.EasyMock;
import org.joda.time.DateTime;
import org.joda.time.Duration;
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.Node;
import org.w3c.dom.NodeList;

import be.fedict.eid.applet.service.signer.AbstractXmlSignatureService;
import be.fedict.eid.applet.service.signer.DigestAlgo;
import be.fedict.eid.applet.service.signer.SignatureFacet;
import be.fedict.eid.applet.service.signer.TemporaryDataStorage;
import be.fedict.eid.applet.service.signer.facets.ExplicitSignaturePolicyService;
import be.fedict.eid.applet.service.signer.facets.RevocationData;
import be.fedict.eid.applet.service.signer.facets.RevocationDataService;
import be.fedict.eid.applet.service.signer.facets.SignaturePolicyService;
import be.fedict.eid.applet.service.signer.facets.XAdESSignatureFacet;
import be.fedict.eid.applet.service.signer.facets.XAdESXLSignatureFacet;
import be.fedict.eid.applet.service.signer.time.TimeStampService;
import be.fedict.eid.applet.service.spi.DigestInfo;
import be.fedict.eid.dss.spi.DSSDocumentContext;
import be.fedict.eid.dss.spi.utils.XAdESUtils;
import be.fedict.eid.dss.spi.utils.XAdESValidation;
import be.fedict.eid.dss.spi.utils.exception.XAdESValidationException;

public class XAdESValidationTest {

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

    private KeyPair keyPair;
    private X509Certificate certificate;

    @BeforeClass
    public static void beforeClass() {
        if (null == Security.getProvider(BouncyCastleProvider.PROVIDER_NAME)) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    @Before
    public void setup() throws Exception {

        this.keyPair = PkiTestUtils.generateKeyPair();
        DateTime notBefore = new DateTime();
        DateTime notAfter = notBefore.plusYears(1);
        this.certificate = PkiTestUtils.generateCertificate(this.keyPair.getPublic(), "CN=Test", notBefore,
                notAfter, null, this.keyPair.getPrivate(), true, 0, null, null,
                new KeyUsage(KeyUsage.nonRepudiation), false);

    }

    @Test
    @SuppressWarnings("unchecked")
    public void testXAdESValidationSuccess() throws Exception {

        // Setup: signed document
        Document signedDocument = getSignedDocument(false);
        Node signatureNode = getSignatureNode(signedDocument);
        XMLSignature xmlSignature = getXmlSignature(signatureNode);

        // Setup: XAdESValidation
        DSSDocumentContext mockDSSDocumentContext = createMock(DSSDocumentContext.class);

        // expectations
        mockDSSDocumentContext.validate((TimeStampToken) EasyMock.anyObject());
        mockDSSDocumentContext.validate((TimeStampToken) EasyMock.anyObject());
        mockDSSDocumentContext.validate((List<X509Certificate>) EasyMock.anyObject(), (Date) EasyMock.anyObject(),
                (List<OCSPResp>) EasyMock.anyObject(), (List<X509CRL>) EasyMock.anyObject());
        // XXX: slow running unit tests might fail here
        expect(mockDSSDocumentContext.getTimestampMaxOffset()).andReturn(20 * 1000L);
        expect(mockDSSDocumentContext.getMaxGracePeriod()).andReturn(1000L * 60 * 60 * 24 * 7);

        // prepare
        replay(mockDSSDocumentContext);

        // Operate: XAdESValidation
        new XAdESValidation(mockDSSDocumentContext).validate(signedDocument, xmlSignature, (Element) signatureNode,
                this.certificate);

        // verify
        verify(mockDSSDocumentContext);
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testXAdESValidationWithTimeStampRevocationData() throws Exception {

        // Setup: signed document
        Document signedDocument = getSignedDocument(true);
        Node signatureNode = getSignatureNode(signedDocument);
        XMLSignature xmlSignature = getXmlSignature(signatureNode);

        // Setup: XAdESValidation
        DSSDocumentContext mockDSSDocumentContext = createMock(DSSDocumentContext.class);

        // expectations
        mockDSSDocumentContext.validate((TimeStampToken) EasyMock.anyObject(),
                (List<OCSPResp>) EasyMock.anyObject(), (List<X509CRL>) EasyMock.anyObject());
        mockDSSDocumentContext.validate((TimeStampToken) EasyMock.anyObject(),
                (List<OCSPResp>) EasyMock.anyObject(), (List<X509CRL>) EasyMock.anyObject());
        mockDSSDocumentContext.validate((List<X509Certificate>) EasyMock.anyObject(), (Date) EasyMock.anyObject(),
                (List<OCSPResp>) EasyMock.anyObject(), (List<X509CRL>) EasyMock.anyObject());
        expect(mockDSSDocumentContext.getTimestampMaxOffset()).andReturn(10 * 1000L);
        expect(mockDSSDocumentContext.getMaxGracePeriod()).andReturn(1000L * 60 * 60 * 24 * 7);

        // prepare
        replay(mockDSSDocumentContext);

        // Operate: XAdESValidation
        new XAdESValidation(mockDSSDocumentContext).validate(signedDocument, xmlSignature, (Element) signatureNode,
                this.certificate);

        // verify
        verify(mockDSSDocumentContext);
    }

    @Test
    public void testXAdESValidationWrongSigningCertificate() throws Exception {

        // Setup: signed document
        Document signedDocument = getSignedDocument(false);
        Node signatureNode = getSignatureNode(signedDocument);
        XMLSignature xmlSignature = getXmlSignature(signatureNode);

        // Setup: wrong certificate
        DateTime notBefore = new DateTime();
        DateTime notAfter = notBefore.plusYears(1);
        X509Certificate wrongCertificate = PkiTestUtils.generateCertificate(this.keyPair.getPublic(),
                "CN=Test Wrong", notBefore, notAfter, null, this.keyPair.getPrivate(), true, 0, null, null,
                new KeyUsage(KeyUsage.nonRepudiation), false);

        // Setup: XAdESValidation
        DSSDocumentContext mockDSSDocumentContext = createMock(DSSDocumentContext.class);

        // prepare
        replay(mockDSSDocumentContext);

        // Operate: XAdESValidation
        try {
            new XAdESValidation(mockDSSDocumentContext).validate(signedDocument, xmlSignature,
                    (Element) signatureNode, wrongCertificate);
            fail();
        } catch (XAdESValidationException e) {
            // expected
            LOG.error(e);
        }

        // verify
        verify(mockDSSDocumentContext);
    }

    @Test
    public void testJodaTime() throws Exception {
        DateTime t1 = new DateTime();
        DateTime t2 = t1.plusSeconds(10);

        assertTrue(t1.isBefore(t2));
        Duration dt = new Duration(t1, t2);
        LOG.debug("dt: " + dt);
        assertFalse(dt.isShorterThan(new Duration(10 * 1000)));
        assertTrue(dt.isShorterThan(new Duration(10 * 1000 + 1)));
    }

    private Document getTestDocument() throws Exception {

        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setNamespaceAware(true);
        DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
        Document document = documentBuilder.newDocument();
        Element rootElement = document.createElementNS("urn:test", "tns:root");
        rootElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:tns", "urn:test");
        document.appendChild(rootElement);
        Element dataElement = document.createElementNS("urn:test", "tns:data");
        dataElement.setAttributeNS(null, "Id", "id-1234");
        dataElement.setTextContent("data to be signed");
        rootElement.appendChild(dataElement);
        return document;
    }

    private Document getSignedDocument(boolean tsaRevocationData) throws Exception {

        // setup
        Document testDocument = getTestDocument();

        // setup: xades signature facets
        SignaturePolicyService signaturePolicyService = new ExplicitSignaturePolicyService("urn:test",
                "hello world".getBytes(), "description", "http://here.com");
        XAdESSignatureFacet xadesSignatureFacet = new XAdESSignatureFacet(signaturePolicyService);
        TimeStampService testTimeStampService = new TestTimeStampService(tsaRevocationData);
        RevocationDataService mockRevocationDataService = EasyMock.createMock(RevocationDataService.class);
        XAdESXLSignatureFacet xadesXLSignatureFacet = new XAdESXLSignatureFacet(testTimeStampService,
                mockRevocationDataService);

        // setup: signature test service
        XmlSignatureTestService testedInstance = new XmlSignatureTestService(xadesSignatureFacet,
                xadesXLSignatureFacet);
        testedInstance.setEnvelopingDocument(testDocument);
        testedInstance.setSignatureDescription("test-signature-description");

        // setup: revocation data, ...
        List<X509Certificate> certificateChain = new LinkedList<X509Certificate>();
        /*
         * We need at least 2 certificates for the XAdES-C complete certificate
         * refs construction.
         */
        certificateChain.add(certificate);
        certificateChain.add(certificate);

        RevocationData revocationData = new RevocationData();
        final X509CRL crl = PkiTestUtils.generateCrl(certificate, keyPair.getPrivate());
        revocationData.addCRL(crl);
        OCSPResp ocspResp = PkiTestUtils.createOcspResp(certificate, false, certificate, certificate,
                keyPair.getPrivate(), "SHA1withRSA");
        revocationData.addOCSP(ocspResp.getEncoded());

        // expectations
        EasyMock.expect(mockRevocationDataService.getRevocationData(EasyMock.eq(certificateChain)))
                .andStubReturn(revocationData);

        // prepare
        EasyMock.replay(mockRevocationDataService);

        // operate
        DigestInfo digestInfo = testedInstance.preSign(null, certificateChain);

        // verify
        assertNotNull(digestInfo);
        LOG.debug("digest info description: " + digestInfo.description);
        assertEquals("test-signature-description", digestInfo.description);
        assertNotNull(digestInfo.digestValue);
        LOG.debug("digest algo: " + digestInfo.digestAlgo);
        assertEquals("SHA-1", digestInfo.digestAlgo);

        TemporaryTestDataStorage temporaryDataStorage = (TemporaryTestDataStorage) testedInstance
                .getTemporaryDataStorage();
        assertNotNull(temporaryDataStorage);
        InputStream tempInputStream = temporaryDataStorage.getTempInputStream();
        assertNotNull(tempInputStream);
        Document tmpDocument = PkiTestUtils.loadDocument(tempInputStream);

        LOG.debug("tmp document: " + PkiTestUtils.toString(tmpDocument));
        Element nsElement = tmpDocument.createElement("ns");
        nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ds", Constants.SignatureSpecNS);
        nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:xades", XAdESUtils.XADES_132_NS_URI);
        Node digestValueNode = XPathAPI.selectSingleNode(tmpDocument, "//ds:DigestValue", nsElement);
        assertNotNull(digestValueNode);
        String digestValueTextContent = digestValueNode.getTextContent();
        LOG.debug("digest value text content: " + digestValueTextContent);
        assertFalse(digestValueTextContent.isEmpty());

        /*
         * Sign the received XML signature digest value.
         */
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate());
        byte[] digestInfoValue = ArrayUtils.addAll(PkiTestUtils.SHA1_DIGEST_INFO_PREFIX, digestInfo.digestValue);
        byte[] signatureValue = cipher.doFinal(digestInfoValue);

        /*
         * Operate: postSign
         */
        testedInstance.postSign(signatureValue, certificateChain);

        byte[] signedDocumentData = testedInstance.getSignedDocumentData();
        assertNotNull(signedDocumentData);

        Document signedDocument = PkiTestUtils.loadDocument(new ByteArrayInputStream(signedDocumentData));
        LOG.debug("signed document: " + PkiTestUtils.toString(signedDocument));
        return signedDocument;
    }

    private XMLSignature getXmlSignature(Node signatureNode) throws Exception {

        DOMValidateContext domValidateContext = new DOMValidateContext(
                KeySelector.singletonKeySelector(keyPair.getPublic()), signatureNode);
        XMLSignatureFactory xmlSignatureFactory = XMLSignatureFactory.getInstance();
        XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext);
        boolean validity = xmlSignature.validate(domValidateContext);
        assertTrue(validity);
        return xmlSignature;
    }

    private Node getSignatureNode(Document signedDocument) {

        NodeList signatureNodeList = signedDocument.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
        assertEquals(1, signatureNodeList.getLength());
        return signatureNodeList.item(0);
    }

    private static class XmlSignatureTestService extends AbstractXmlSignatureService {

        private Document envelopingDocument;

        private TemporaryTestDataStorage temporaryDataStorage;

        private String signatureDescription;

        private ByteArrayOutputStream signedDocumentOutputStream;

        public XmlSignatureTestService(SignatureFacet... signatureFacets) {
            super(DigestAlgo.SHA1);
            this.temporaryDataStorage = new TemporaryTestDataStorage();
            this.signedDocumentOutputStream = new ByteArrayOutputStream();
            for (SignatureFacet signatureFacet : signatureFacets) {
                addSignatureFacet(signatureFacet);
            }
            setSignatureNamespacePrefix("ds");
        }

        public byte[] getSignedDocumentData() {
            return this.signedDocumentOutputStream.toByteArray();
        }

        public void setEnvelopingDocument(Document envelopingDocument) {
            this.envelopingDocument = envelopingDocument;
        }

        @Override
        protected Document getEnvelopingDocument() {
            return this.envelopingDocument;
        }

        @Override
        protected String getSignatureDescription() {
            return this.signatureDescription;
        }

        public void setSignatureDescription(String signatureDescription) {
            this.signatureDescription = signatureDescription;
        }

        @Override
        protected OutputStream getSignedDocumentOutputStream() {
            return this.signedDocumentOutputStream;
        }

        @Override
        protected TemporaryDataStorage getTemporaryDataStorage() {
            return this.temporaryDataStorage;
        }

        public String getFilesDigestAlgorithm() {
            return null;
        }

        public void setHttpSessionObject(Object sessionObject) {

        }
    }

    private class TestTimeStampService implements TimeStampService {

        private KeyPair tsaKeyPair;
        private List<X509Certificate> tsaCertificateChain;
        private X509CRL tsaCrl;

        public TestTimeStampService(boolean tsaRevocationData) throws Exception {

            this.tsaKeyPair = PkiTestUtils.generateKeyPair();
            DateTime notBefore = new DateTime();
            DateTime notAfter = notBefore.plusYears(1);
            X509Certificate tsaCertificate = PkiTestUtils.generateCertificate(this.tsaKeyPair.getPublic(),
                    "CN=Test TSA", notBefore, notAfter, null, this.tsaKeyPair.getPrivate(), true, 0, null, null,
                    null, true);
            this.tsaCertificateChain = new LinkedList<X509Certificate>();
            this.tsaCertificateChain.add(tsaCertificate);

            if (tsaRevocationData) {
                this.tsaCrl = PkiTestUtils.generateCrl(tsaCertificate, this.tsaKeyPair.getPrivate());
            }
        }

        @Override
        public byte[] timeStamp(byte[] bytes, RevocationData revocationData) throws Exception {

            TimeStampToken timeStampToken = PkiTestUtils.createTimeStampToken(bytes, this.tsaKeyPair.getPrivate(),
                    this.tsaCertificateChain);

            if (null != this.tsaCrl) {
                revocationData.addCRL(this.tsaCrl);
            }

            return timeStampToken.getEncoded();
        }

        @Override
        public String getTimeStampServiceURL() {
            return "";
        }
    }

}