be.fedict.eid.dss.spi.utils.XAdESValidation.java Source code

Java tutorial

Introduction

Here is the source code for be.fedict.eid.dss.spi.utils.XAdESValidation.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 be.fedict.eid.dss.spi.utils;

import java.security.cert.CertificateEncodingException;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.datatype.XMLGregorianCalendar;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xml.security.utils.Constants;
import org.bouncycastle.ocsp.OCSPResp;
import org.bouncycastle.tsp.TimeStampToken;
import org.joda.time.DateTime;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import be.fedict.eid.applet.service.signer.facets.IdentitySignatureFacet;
import be.fedict.eid.applet.service.signer.jaxb.identity.IdentityType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.AnyType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.CertificateValuesType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.ClaimedRolesListType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.CompleteCertificateRefsType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.CompleteRevocationRefsType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.QualifyingPropertiesType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.RevocationValuesType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.SignedPropertiesType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.SignedSignaturePropertiesType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.SignerRoleType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.XAdESTimeStampType;
import be.fedict.eid.applet.service.signer.jaxb.xades141.ValidationDataType;
import be.fedict.eid.dss.spi.DSSDocumentContext;
import be.fedict.eid.dss.spi.SignatureInfo;
import be.fedict.eid.dss.spi.utils.exception.XAdESValidationException;

/**
 * XAdES-X-L v1.4.1 validation utility class. Can be shared between different
 * document service implementations.
 * 
 * @author Frank Cornelis
 */
public class XAdESValidation {

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

    private final DSSDocumentContext documentContext;

    public XAdESValidation(DSSDocumentContext documentContext) {
        this.documentContext = documentContext;
    }

    public void prepareDocument(Element signatureElement) {
        prepareDocumentXades(signatureElement);
        prepareDocumentIdentity(signatureElement);
    }

    private void prepareDocumentXades(Element signatureElement) {
        NodeList nodeList = signatureElement.getElementsByTagNameNS("http://uri.etsi.org/01903/v1.3.2#",
                "SignedProperties");
        if (1 == nodeList.getLength()) {
            Element signedPropertiesElement = (Element) nodeList.item(0);
            signedPropertiesElement.setIdAttribute("Id", true);
        }
    }

    private void prepareDocumentIdentity(Element signatureElement) {
        NodeList nodeList = signatureElement.getElementsByTagNameNS("be:fedict:eid:dss:stylesheet:1.0",
                "StyleSheet");
        if (1 == nodeList.getLength()) {
            Element styleSheetElement = (Element) nodeList.item(0);
            styleSheetElement.setIdAttribute("Id", true);
        }
    }

    public SignatureInfo validate(Document document, XMLSignature xmlSignature, Element signatureElement,
            X509Certificate signingCertificate) throws XAdESValidationException {

        try {
            /*
             * Get signing time from XAdES-BES extension.
             */
            Element nsElement = getNsElement(document);

            Element qualifyingPropertiesElement = XAdESUtils.findQualifyingPropertiesElement(nsElement,
                    xmlSignature, signatureElement);
            if (null == qualifyingPropertiesElement) {
                throw new XAdESValidationException("no matching xades:QualifyingProperties present");
            }
            QualifyingPropertiesType qualifyingProperties = XAdESUtils.unmarshall(qualifyingPropertiesElement,
                    QualifyingPropertiesType.class);
            if (false == qualifyingProperties.getTarget().equals("#" + xmlSignature.getId())) {
                throw new XAdESValidationException("xades:QualifyingProperties/@Target incorrect");
            }

            SignedPropertiesType signedProperties = qualifyingProperties.getSignedProperties();
            SignedSignaturePropertiesType signedSignatureProperties = signedProperties
                    .getSignedSignatureProperties();
            XMLGregorianCalendar signingTimeXMLGregorianCalendar = signedSignatureProperties.getSigningTime();
            DateTime signingTime = new DateTime(signingTimeXMLGregorianCalendar.toGregorianCalendar().getTime());
            LOG.debug("XAdES signing time: " + signingTime);

            /*
             * Check the XAdES signing certificate
             */
            XAdESUtils.checkSigningCertificate(signingCertificate, signedSignatureProperties);

            /*
             * Get XAdES ClaimedRole.
             */
            String role = null;
            SignerRoleType signerRole = signedSignatureProperties.getSignerRole();
            if (null != signerRole) {
                ClaimedRolesListType claimedRolesList = signerRole.getClaimedRoles();
                if (null != claimedRolesList) {
                    List<AnyType> claimedRoles = claimedRolesList.getClaimedRole();
                    if (!claimedRoles.isEmpty()) {
                        AnyType claimedRole = claimedRoles.get(0);
                        List<Object> claimedRoleContent = claimedRole.getContent();
                        for (Object claimedRoleContentItem : claimedRoleContent) {
                            if (claimedRoleContentItem instanceof String) {
                                role = (String) claimedRoleContentItem;
                                LOG.debug("XAdES claimed role: " + role);
                                break;
                            }
                        }
                    }
                }
            }

            // XAdES-T

            // validate first SignatureTimeStamp
            Element signatureTimeStampElement = XAdESUtils
                    .findUnsignedSignaturePropertyElement(qualifyingPropertiesElement, "SignatureTimeStamp");
            if (null == signatureTimeStampElement) {
                throw new XAdESValidationException("no xades:SignatureTimeStamp present");
            }
            XAdESTimeStampType signatureTimeStamp = XAdESUtils.unmarshall(signatureTimeStampElement,
                    XAdESTimeStampType.class);
            List<TimeStampToken> signatureTimeStampTokens = XAdESSignatureTimeStampValidation
                    .verify(signatureTimeStamp, signatureElement);

            // XAdES-X

            // validate first SigAndRefsTimeStamp
            Element sigAndRefsTimeStampElement = XAdESUtils
                    .findUnsignedSignaturePropertyElement(qualifyingPropertiesElement, "SigAndRefsTimeStamp");
            if (null == sigAndRefsTimeStampElement) {
                LOG.error("No SigAndRefsTimeStamp present");
                throw new XAdESValidationException("no xades:SigAndRefsTimeStamp present");
            }
            XAdESTimeStampType sigAndRefsTimeStamp = XAdESUtils.unmarshall(sigAndRefsTimeStampElement,
                    XAdESTimeStampType.class);
            List<TimeStampToken> sigAndRefsTimeStampTokens = XAdESSigAndRefsTimeStampValidation
                    .verify(sigAndRefsTimeStamp, signatureElement);

            // timestamp tokens trust validation
            LOG.debug("validate SignatureTimeStamp's trust...");
            ValidationDataType signatureTimeStampValidationData = XAdESUtils.findNextSibling(
                    signatureTimeStampElement, XAdESUtils.XADES_141_NS_URI, "TimeStampValidationData",
                    ValidationDataType.class);
            if (null != signatureTimeStampValidationData) {
                LOG.debug("xadesv141:TimeStampValidationData present for xades:SignatureTimeStamp");
                RevocationValuesType revocationValues = signatureTimeStampValidationData.getRevocationValues();
                List<X509CRL> crls = XAdESUtils.getCrls(revocationValues);
                List<OCSPResp> ocspResponses = XAdESUtils.getOCSPResponses(revocationValues);
                for (TimeStampToken signatureTimeStampToken : signatureTimeStampTokens) {
                    this.documentContext.validate(signatureTimeStampToken, ocspResponses, crls);
                }
            } else {
                for (TimeStampToken signatureTimeStampToken : signatureTimeStampTokens) {
                    this.documentContext.validate(signatureTimeStampToken);
                }
            }

            LOG.debug("validate SigAndRefsTimeStamp's trust...");
            ValidationDataType sigAndRefsTimeStampValidationData = XAdESUtils.findNextSibling(
                    sigAndRefsTimeStampElement, XAdESUtils.XADES_141_NS_URI, "TimeStampValidationData",
                    ValidationDataType.class);
            if (null != sigAndRefsTimeStampValidationData) {
                LOG.debug("xadesv141:TimeStampValidationData present for xades:SigAndRefsTimeStamp");
                RevocationValuesType revocationValues = sigAndRefsTimeStampValidationData.getRevocationValues();
                List<X509CRL> crls = XAdESUtils.getCrls(revocationValues);
                List<OCSPResp> ocspResponses = XAdESUtils.getOCSPResponses(revocationValues);
                for (TimeStampToken sigAndRefsTimeStampToken : sigAndRefsTimeStampTokens) {
                    this.documentContext.validate(sigAndRefsTimeStampToken, ocspResponses, crls);
                }
            } else {
                for (TimeStampToken sigAndRefsTimeStampToken : sigAndRefsTimeStampTokens) {
                    this.documentContext.validate(sigAndRefsTimeStampToken);
                }
            }

            // timestamp tokens time coherence verification
            long timestampMaxOffset = this.documentContext.getTimestampMaxOffset();
            LOG.debug("validate timestamp tokens time coherence...");
            for (TimeStampToken signatureTimeStampToken : signatureTimeStampTokens) {
                DateTime stsTokenGenTime = new DateTime(signatureTimeStampToken.getTimeStampInfo().getGenTime());
                try {
                    XAdESUtils.checkCloseEnough(signingTime, stsTokenGenTime, timestampMaxOffset);
                } catch (XAdESValidationException e) {
                    throw new XAdESValidationException("SignatureTimeStamp too far from SigningTime", e);
                }

                for (TimeStampToken sigAndRefsTimeStampToken : sigAndRefsTimeStampTokens) {
                    DateTime sigAndRefsTokenGenTime = new DateTime(
                            sigAndRefsTimeStampToken.getTimeStampInfo().getGenTime());
                    if (sigAndRefsTokenGenTime.isBefore(stsTokenGenTime)) {
                        throw new XAdESValidationException("SigAndRefsTimeStamp before SignatureTimeStamp");
                    }
                }
            }

            long maxGracePeriod = this.documentContext.getMaxGracePeriod();
            for (TimeStampToken sigAndRefsTimeStampToken : sigAndRefsTimeStampTokens) {
                DateTime sigAndRefsTokenGenTime = new DateTime(
                        sigAndRefsTimeStampToken.getTimeStampInfo().getGenTime());
                try {
                    XAdESUtils.checkCloseEnough(signingTime, sigAndRefsTokenGenTime,
                            maxGracePeriod * 1000 * 60 * 60);
                } catch (XAdESValidationException e) {
                    throw new XAdESValidationException("SigAndRefsTimeStamp too far from SigningTime", e);
                }
            }

            // XAdES-X-L

            /*
             * Retrieve certificate chain and revocation data from XAdES-X-L
             * extension for trust validation.
             */
            RevocationValuesType revocationValues = XAdESUtils.findUnsignedSignatureProperty(qualifyingProperties,
                    RevocationValuesType.class, "RevocationValues");
            List<X509CRL> crls = XAdESUtils.getCrls(revocationValues);
            List<OCSPResp> ocspResponses = XAdESUtils.getOCSPResponses(revocationValues);

            CertificateValuesType certificateValues = XAdESUtils.findUnsignedSignatureProperty(qualifyingProperties,
                    CertificateValuesType.class, "CertificateValues");
            if (null == certificateValues) {
                throw new XAdESValidationException("no CertificateValues element found.");
            }
            List<X509Certificate> certificateChain = XAdESUtils.getCertificates(certificateValues);
            if (certificateChain.isEmpty()) {
                throw new XAdESValidationException("no cert chain in CertificateValues");
            }

            /*
             * Check certificate chain is indeed contains the signing
             * certificate.
             */
            if (!Arrays.equals(signingCertificate.getEncoded(), certificateChain.get(0).getEncoded())) {
                // throw new XAdESValidationException(
                // "XAdES certificate chain does not include actual signing certificate");
                /*
                 * Not all XAdES implementations add the entire certificate
                 * chain via xades:CertificateValues.
                 */
                certificateChain.add(0, signingCertificate);
            }
            LOG.debug("XAdES certificate chain contains actual signing certificate");

            // XAdES-C
            CompleteCertificateRefsType completeCertificateRefs = XAdESUtils.findUnsignedSignatureProperty(
                    qualifyingProperties, CompleteCertificateRefsType.class, "CompleteCertificateRefs");
            if (null == completeCertificateRefs) {
                throw new XAdESValidationException("missing CompleteCertificateRefs");
            }
            CompleteRevocationRefsType completeRevocationRefs = XAdESUtils.findUnsignedSignatureProperty(
                    qualifyingProperties, CompleteRevocationRefsType.class, "CompleteRevocationRefs");
            if (null == completeRevocationRefs) {
                throw new XAdESValidationException("missing CompleteRevocationRefs");
            }
            for (OCSPResp ocspResp : ocspResponses) {
                XAdESUtils.checkReference(ocspResp, completeRevocationRefs);
            }
            for (X509CRL crl : crls) {
                XAdESUtils.checkReference(crl, completeRevocationRefs);
            }
            Iterator<X509Certificate> certIterator = certificateChain.iterator();
            certIterator.next(); // digestion of SigningCertificate already
                                 // checked
            while (certIterator.hasNext()) {
                X509Certificate certificate = certIterator.next();
                XAdESUtils.checkReference(certificate, completeCertificateRefs);
            }

            /*
             * Perform trust validation via eID Trust Service
             */
            this.documentContext.validate(certificateChain, signingTime.toDate(), ocspResponses, crls);

            /*
             * Retrieve the possible eID identity signature extension data.
             */
            String firstName = null;
            String name = null;
            String middleName = null;
            SignatureInfo.Gender gender = null;
            byte[] photo = null;

            IdentityType identity = XAdESUtils.findIdentity(nsElement, xmlSignature, signatureElement);
            if (null != identity) {
                firstName = identity.getFirstName();
                name = identity.getName();
                middleName = identity.getMiddleName();
                switch (identity.getGender()) {
                case MALE:
                    gender = SignatureInfo.Gender.MALE;
                    break;
                case FEMALE:
                    gender = SignatureInfo.Gender.FEMALE;
                    break;
                }
                photo = identity.getPhoto().getValue();
            }

            /*
             * Return the result of the signature analysis.
             */
            return new SignatureInfo(signingCertificate, signingTime.toDate(), role, firstName, name, middleName,
                    gender, photo);
        } catch (CertificateEncodingException e) {
            throw new XAdESValidationException(e);
        } catch (Exception e) {
            throw new XAdESValidationException(e);
        }
    }

    private Element getNsElement(Document document) {

        Element nsElement = document.createElement("nsElement");
        nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ds", Constants.SignatureSpecNS);
        nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:xades", XAdESUtils.XADES_132_NS_URI);
        nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:xades141", XAdESUtils.XADES_141_NS_URI);
        nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:identity", IdentitySignatureFacet.NAMESPACE_URI);
        return nsElement;
    }
}