be.fedict.trust.client.XKMS2Client.java Source code

Java tutorial

Introduction

Here is the source code for be.fedict.trust.client.XKMS2Client.java

Source

/*
 * eID Trust Service Project.
 * Copyright (C) 2009-2012 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.trust.client;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.ProxySelector;
import java.security.InvalidKeyException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.security.cert.CRLException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.ws.Binding;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.handler.Handler;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bouncycastle.ocsp.OCSPResp;
import org.bouncycastle.tsp.TimeStampToken;
import org.bouncycastle.x509.X509V2AttributeCertificate;

import sun.security.timestamp.TimestampToken;
import be.fedict.trust.client.exception.RevocationDataCorruptException;
import be.fedict.trust.client.exception.RevocationDataNotFoundException;
import be.fedict.trust.client.exception.TrustDomainNotFoundException;
import be.fedict.trust.client.exception.ValidationFailedException;
import be.fedict.trust.client.jaxb.xades132.CRLValuesType;
import be.fedict.trust.client.jaxb.xades132.CertifiedRolesListType;
import be.fedict.trust.client.jaxb.xades132.EncapsulatedPKIDataType;
import be.fedict.trust.client.jaxb.xades132.OCSPValuesType;
import be.fedict.trust.client.jaxb.xades132.RevocationValuesType;
import be.fedict.trust.client.jaxb.xkms.KeyBindingType;
import be.fedict.trust.client.jaxb.xkms.MessageExtensionAbstractType;
import be.fedict.trust.client.jaxb.xkms.QueryKeyBindingType;
import be.fedict.trust.client.jaxb.xkms.StatusType;
import be.fedict.trust.client.jaxb.xkms.TimeInstantType;
import be.fedict.trust.client.jaxb.xkms.UseKeyWithType;
import be.fedict.trust.client.jaxb.xkms.ValidateRequestType;
import be.fedict.trust.client.jaxb.xkms.ValidateResultType;
import be.fedict.trust.client.jaxb.xmldsig.KeyInfoType;
import be.fedict.trust.client.jaxb.xmldsig.X509DataType;
import be.fedict.trust.client.jaxws.xkms.XKMSPortType;
import be.fedict.trust.client.jaxws.xkms.XKMSService;
import be.fedict.trust.xkms.extensions.AttributeCertificateMessageExtensionType;
import be.fedict.trust.xkms.extensions.RevocationDataMessageExtensionType;
import be.fedict.trust.xkms.extensions.TSAMessageExtensionType;
import be.fedict.trust.xkms2.LoggingSoapHandler;
import be.fedict.trust.xkms2.ResultMajorCode;
import be.fedict.trust.xkms2.ResultMinorCode;
import be.fedict.trust.xkms2.XKMSConstants;
import be.fedict.trust.xkms2.XKMSServiceFactory;

/**
 * Client component for the eID Trust Service XKMS2 web service.
 * 
 * @author Frank Cornelis
 */
public class XKMS2Client {

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

    private final XKMSPortType port;

    private RevocationValuesType revocationValues;

    private WSSecurityClientHandler wsSecurityClientHandler;

    protected List<String> invalidReasonURIs;

    private String location;

    private static XKMS2ProxySelector proxySelector;

    static {
        ProxySelector defaultProxySelector = ProxySelector.getDefault();
        XKMS2Client.proxySelector = new XKMS2ProxySelector(defaultProxySelector);
        ProxySelector.setDefault(XKMS2Client.proxySelector);
    }

    /**
     * Main constructor
     * 
     * @param location
     *            location ( complete path ) of the XKMS2 web service
     */
    public XKMS2Client(String location) {
        this.location = location;
        this.invalidReasonURIs = new LinkedList<String>();

        XKMSService xkmsService = XKMSServiceFactory.getInstance();
        this.port = xkmsService.getXKMSPort();

        registeredWSSecurityHandler(this.port);
        setEndpointAddress(location);
    }

    /**
     * Enables/disables logging of all SOAP requests/responses.
     * 
     * @param logging
     *            enable logging on or not
     */
    public void setLogging(boolean logging) {
        if (logging) {
            registerLoggerHandler(this.port);
        } else {
            removeLoggerHandler(this.port);
        }
    }

    /**
     * Proxy configuration setting ( both http as https ).
     * 
     * @param proxyHost
     *            proxy host
     * @param proxyPort
     *            proxy port
     */
    public void setProxy(String proxyHost, int proxyPort) {
        XKMS2Client.proxySelector.setProxy(this.location, proxyHost, proxyPort);
    }

    /**
     * Set the optional server {@link X509Certificate}. If specified and the
     * trust service has WS-Security message signing configured, the incoming
     * {@link X509Certificate} will be checked against the specified
     * {@link X509Certificate}.
     * 
     * @param serverCertificate
     *            the server X509 certificate.
     */
    public void setServerCertificate(X509Certificate serverCertificate) {
        this.wsSecurityClientHandler.setServerCertificate(serverCertificate);
    }

    /**
     * If set, unilateral TLS authentication will occurs, verifying the server
     * {@link X509Certificate} specified {@link PublicKey}.
     * <p/>
     * WARNING: only works when using the JAX-WS RI.
     * 
     * @param publicKey
     *            public key to validate server TLS certificate against.
     */
    public void setServicePublicKey(final PublicKey publicKey) {
        // Create TrustManager
        TrustManager[] trustManager = { new X509TrustManager() {

            public X509Certificate[] getAcceptedIssuers() {

                return null;
            }

            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

                X509Certificate serverCertificate = chain[0];
                LOG.debug("server X509 subject: " + serverCertificate.getSubjectX500Principal().toString());
                LOG.debug("authentication type: " + authType);
                if (null == publicKey) {
                    LOG.warn("not performing any server certificate validation at all");
                    return;
                }

                try {
                    serverCertificate.verify(publicKey);
                    LOG.debug("valid server certificate");
                } catch (InvalidKeyException e) {
                    throw new CertificateException("Invalid Key");
                } catch (NoSuchAlgorithmException e) {
                    throw new CertificateException("No such algorithm");
                } catch (NoSuchProviderException e) {
                    throw new CertificateException("No such provider");
                } catch (SignatureException e) {
                    throw new CertificateException("Wrong signature");
                }
            }

            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

                throw new CertificateException("this trust manager cannot be used as server-side trust manager");
            }
        } };

        // Create SSL Context
        try {
            SSLContext sslContext = SSLContext.getInstance("TLS");
            SecureRandom secureRandom = new SecureRandom();
            sslContext.init(null, trustManager, secureRandom);
            LOG.debug("SSL context provider: " + sslContext.getProvider().getName());

            // Setup TrustManager for validation
            Map<String, Object> requestContext = ((BindingProvider) this.port).getRequestContext();
            requestContext.put("com.sun.xml.ws.transport.https.client.SSLSocketFactory",
                    sslContext.getSocketFactory());

        } catch (KeyManagementException e) {
            String msg = "key management error: " + e.getMessage();
            LOG.error(msg, e);
            throw new RuntimeException(msg, e);
        } catch (NoSuchAlgorithmException e) {
            String msg = "TLS algo not present: " + e.getMessage();
            LOG.error(msg, e);
            throw new RuntimeException(msg, e);
        }
    }

    private void setEndpointAddress(String location) {
        LOG.debug("ws location: " + location);
        if (null == location) {
            throw new IllegalArgumentException("XKMS2 location URL cannot be null");
        }
        BindingProvider bindingProvider = (BindingProvider) this.port;
        bindingProvider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, location);
    }

    /**
     * Registers the logging SOAP handler on the given JAX-WS port component.
     */
    private void registerLoggerHandler(Object port) {
        BindingProvider bindingProvider = (BindingProvider) port;
        Binding binding = bindingProvider.getBinding();
        @SuppressWarnings("rawtypes")
        List<Handler> handlerChain = binding.getHandlerChain();
        handlerChain.add(new LoggingSoapHandler());
        binding.setHandlerChain(handlerChain);
    }

    /**
     * Unregister possible logging SOAP handlers on the given JAX-WS port
     * component.
     */
    private void removeLoggerHandler(Object port) {
        BindingProvider bindingProvider = (BindingProvider) port;
        Binding binding = bindingProvider.getBinding();
        List<Handler> handlerChain = binding.getHandlerChain();
        Iterator<Handler> iter = handlerChain.iterator();
        while (iter.hasNext()) {
            Handler handler = iter.next();
            if (handler instanceof LoggingSoapHandler) {
                iter.remove();
            }
        }
        binding.setHandlerChain(handlerChain);
    }

    private void registeredWSSecurityHandler(Object port) {
        BindingProvider bindingProvider = (BindingProvider) port;
        Binding binding = bindingProvider.getBinding();
        @SuppressWarnings("rawtypes")
        List<Handler> handlerChain = binding.getHandlerChain();
        this.wsSecurityClientHandler = new WSSecurityClientHandler();
        handlerChain.add(this.wsSecurityClientHandler);
        binding.setHandlerChain(handlerChain);
    }

    /**
     * Validate the specified certificate chain against the default trust domain
     * configured at the trust service we are connecting to.
     */
    public void validate(List<X509Certificate> certificateChain) throws CertificateEncodingException,
            TrustDomainNotFoundException, RevocationDataNotFoundException, ValidationFailedException {
        validate(null, certificateChain);
    }

    /**
     * Validates the given certificate chain against the default trust domain
     * configured at the trust service we are connecting to.
     * 
     * @param certificateChain
     *            the certificate chain to be validated.
     * @throws CertificateEncodingException
     * @throws TrustDomainNotFoundException
     * @throws RevocationDataNotFoundException
     * @throws ValidationFailedException
     */
    public void validate(Certificate[] certificateChain) throws CertificateEncodingException,
            TrustDomainNotFoundException, RevocationDataNotFoundException, ValidationFailedException {
        List<X509Certificate> x509CertificateChain = new LinkedList<X509Certificate>();
        for (Certificate certificate : certificateChain) {
            X509Certificate x509Certificate = (X509Certificate) certificate;
            x509CertificateChain.add(x509Certificate);
        }
        validate(x509CertificateChain);
    }

    /**
     * Validate the specified certificate chain against the default trust domain
     * configured at the trust service we are connecting to.
     * <p/>
     * The used revocation data can be retrieved using
     * {@link #getRevocationValues()}.
     * 
     * @param returnRevocationData
     *            whether or not the used revocation data should be returned.
     */
    public void validate(List<X509Certificate> certificateChain, boolean returnRevocationData)
            throws CertificateEncodingException, TrustDomainNotFoundException, RevocationDataNotFoundException,
            ValidationFailedException {
        validate(null, certificateChain, returnRevocationData);
    }

    /**
     * Validate the specified certificate chain against the specified trust
     * domain.
     */
    public void validate(String trustDomain, List<X509Certificate> certificateChain)
            throws CertificateEncodingException, TrustDomainNotFoundException, RevocationDataNotFoundException,
            ValidationFailedException {
        validate(trustDomain, certificateChain, false);
    }

    /**
     * Validate the specified certificate chain against the specified trust
     * domain.
     * <p/>
     * The used revocation data can be retrieved using
     * {@link #getRevocationValues()}.
     * 
     * @param returnRevocationData
     *            whether or not the used revocation data should be returned.
     */
    public void validate(String trustDomain, List<X509Certificate> certificateChain, boolean returnRevocationData)
            throws CertificateEncodingException, TrustDomainNotFoundException, RevocationDataNotFoundException,
            ValidationFailedException {
        validate(trustDomain, certificateChain, returnRevocationData, null, null, null, null, null, null);
    }

    /**
     * Validate the specified certificate chain against the specified trust
     * domain using historical validation using the specified revocation data.
     */
    public void validate(String trustDomain, List<X509Certificate> certificateChain, Date validationDate,
            List<OCSPResp> ocspResponses, List<X509CRL> crls) throws CertificateEncodingException,
            TrustDomainNotFoundException, RevocationDataNotFoundException, ValidationFailedException {
        if ((null == ocspResponses || ocspResponses.isEmpty()) && (null == crls || crls.isEmpty())) {
            LOG.error("No revocation data for historical validation.");
            throw new RevocationDataNotFoundException();
        }

        try {
            List<byte[]> encodedOcspResponses = new LinkedList<byte[]>();
            List<byte[]> encodedCrls = new LinkedList<byte[]>();
            for (OCSPResp ocspResponse : ocspResponses) {
                encodedOcspResponses.add(ocspResponse.getEncoded());
            }
            for (X509CRL crl : crls) {
                encodedCrls.add(crl.getEncoded());
            }

            validate(trustDomain, certificateChain, false, validationDate, encodedOcspResponses, encodedCrls, null,
                    null, null);
        } catch (IOException e) {
            LOG.error("Failed to get encoded OCSPResponse: " + e.getMessage(), e);
            throw new RuntimeException(e);
        } catch (CRLException e) {
            LOG.error("Failed to get encoded CRL: " + e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }

    /**
     * Validate the specified certificate chain against the specified trust
     * domain using historical validation using the specified revocation data.
     */
    public void validateEncoded(String trustDomain, List<X509Certificate> certificateChain, Date validationDate,
            List<byte[]> ocspResponses, List<byte[]> crls)
            throws CertificateEncodingException, TrustDomainNotFoundException, RevocationDataNotFoundException,
            ValidationFailedException, RevocationDataCorruptException {
        if ((null == ocspResponses || ocspResponses.isEmpty()) && (null == crls || crls.isEmpty())) {
            LOG.error("No revocation data for historical validation.");
            throw new RevocationDataNotFoundException();
        }

        try {
            // check encoded OCSP response are valid
            for (byte[] encodedOcspResponse : ocspResponses) {
                new OCSPResp(encodedOcspResponse);
            }
            // check encoded CRLs are valid
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            for (byte[] encodedCrl : crls) {
                ByteArrayInputStream bais = new ByteArrayInputStream(encodedCrl);
                certificateFactory.generateCRL(bais);
            }
        } catch (IOException e) {
            throw new RevocationDataCorruptException("Invalid OCSP response", e);
        } catch (CRLException e) {
            throw new RevocationDataCorruptException("Invalid CRL", e);
        } catch (CertificateException e) {
            throw new RevocationDataCorruptException(e);
        }

        validate(trustDomain, certificateChain, false, validationDate, ocspResponses, crls, null, null, null);
    }

    /**
     * Validate the specified certificate chain against the specified trust
     * domain using historical validation using the specified revocation data.
     */
    public void validate(String trustDomain, List<X509Certificate> certificateChain, Date validationDate,
            RevocationValuesType revocationValues) throws CertificateEncodingException,
            TrustDomainNotFoundException, RevocationDataNotFoundException, ValidationFailedException {
        if (null == revocationValues) {
            LOG.error("No revocation data for historical validation.");
            throw new RevocationDataNotFoundException();
        }

        validate(trustDomain, certificateChain, false, validationDate, null, null, revocationValues, null, null);
    }

    /**
     * Validate the specified {@link TimestampToken} for the specified TSA trust
     * domain
     */
    public void validate(String trustDomain, TimeStampToken timeStampToken) throws TrustDomainNotFoundException,
            CertificateEncodingException, RevocationDataNotFoundException, ValidationFailedException {
        LOG.debug("validate timestamp token");
        validate(trustDomain, new LinkedList<X509Certificate>(), false, null, null, null, revocationValues,
                timeStampToken, null);
    }

    /**
     * Validate the specified
     * {@link be.fedict.trust.client.jaxb.xades132.EncapsulatedPKIDataType}
     * holding the {@link X509V2AttributeCertificate}.
     * 
     * @param certificateChain
     *            the certificate chain for the attribute certificate
     */
    public void validate(String trustDomain, List<X509Certificate> certificateChain,
            CertifiedRolesListType attributeCertificates) throws CertificateEncodingException,
            TrustDomainNotFoundException, RevocationDataNotFoundException, ValidationFailedException {
        LOG.debug("validate attribute certificate");
        validate(trustDomain, certificateChain, false, null, null, null, revocationValues, null,
                attributeCertificates);
    }

    protected void validate(String trustDomain, List<X509Certificate> certificateChain,
            boolean returnRevocationData, Date validationDate, List<byte[]> ocspResponses, List<byte[]> crls,
            RevocationValuesType revocationValues, TimeStampToken timeStampToken,
            CertifiedRolesListType attributeCertificates) throws CertificateEncodingException,
            TrustDomainNotFoundException, RevocationDataNotFoundException, ValidationFailedException {
        LOG.debug("validate");
        be.fedict.trust.client.jaxb.xkms.ObjectFactory objectFactory = new be.fedict.trust.client.jaxb.xkms.ObjectFactory();
        be.fedict.trust.client.jaxb.xmldsig.ObjectFactory xmldsigObjectFactory = new be.fedict.trust.client.jaxb.xmldsig.ObjectFactory();

        ValidateRequestType validateRequest = objectFactory.createValidateRequestType();
        QueryKeyBindingType queryKeyBinding = objectFactory.createQueryKeyBindingType();
        KeyInfoType keyInfo = xmldsigObjectFactory.createKeyInfoType();
        queryKeyBinding.setKeyInfo(keyInfo);
        X509DataType x509Data = xmldsigObjectFactory.createX509DataType();
        for (X509Certificate certificate : certificateChain) {
            byte[] encodedCertificate = certificate.getEncoded();
            x509Data.getX509IssuerSerialOrX509SKIOrX509SubjectName()
                    .add(xmldsigObjectFactory.createX509DataTypeX509Certificate(encodedCertificate));
        }
        keyInfo.getContent().add(xmldsigObjectFactory.createX509Data(x509Data));
        validateRequest.setQueryKeyBinding(queryKeyBinding);

        /*
         * Set optional trust domain
         */
        if (null != trustDomain) {
            UseKeyWithType useKeyWith = objectFactory.createUseKeyWithType();
            useKeyWith.setApplication(XKMSConstants.TRUST_DOMAIN_APPLICATION_URI);
            useKeyWith.setIdentifier(trustDomain);
            queryKeyBinding.getUseKeyWith().add(useKeyWith);
        }

        /*
         * Add timestamp token for TSA validation
         */

        if (null != timeStampToken) {
            addTimeStampToken(validateRequest, timeStampToken);
        }

        /*
         * Add attribute certificates
         */
        if (null != attributeCertificates) {
            addAttributeCertificates(validateRequest, attributeCertificates);
        }

        /*
         * Set if used revocation data should be returned or not
         */
        if (returnRevocationData) {
            validateRequest.getRespondWith().add(XKMSConstants.RETURN_REVOCATION_DATA_URI);
        }

        /*
         * Historical validation, add the revocation data to the request
         */
        if (null != validationDate) {
            TimeInstantType timeInstant = objectFactory.createTimeInstantType();
            timeInstant.setTime(getXmlGregorianCalendar(validationDate));
            queryKeyBinding.setTimeInstant(timeInstant);

            addRevocationData(validateRequest, ocspResponses, crls, revocationValues);
        }

        ValidateResultType validateResult = this.port.validate(validateRequest);

        if (null == validateResult) {
            throw new RuntimeException("missing ValidateResult element");
        }

        checkResponse(validateResult);

        // set the optionally requested revocation data
        if (returnRevocationData) {
            for (MessageExtensionAbstractType messageExtension : validateResult.getMessageExtension()) {
                if (messageExtension instanceof RevocationDataMessageExtensionType) {
                    this.revocationValues = ((RevocationDataMessageExtensionType) messageExtension)
                            .getRevocationValues();
                }
            }
            if (null == this.revocationValues) {
                LOG.error("no revocation data found");
                throw new RevocationDataNotFoundException();
            }
        }

        // store reason URIs
        this.invalidReasonURIs.clear();
        List<KeyBindingType> keyBindings = validateResult.getKeyBinding();
        for (KeyBindingType keyBinding : keyBindings) {
            StatusType status = keyBinding.getStatus();
            String statusValue = status.getStatusValue();
            LOG.debug("status: " + statusValue);
            if (XKMSConstants.KEY_BINDING_STATUS_VALID_URI.equals(statusValue)) {
                return;
            }
            for (String invalidReason : status.getInvalidReason()) {
                this.invalidReasonURIs.add(invalidReason);
            }
            throw new ValidationFailedException(invalidReasonURIs);
        }
    }

    /**
     * Add revocation data either from list of {@link OCSPResp} objects and
     * {@link X509CRL} objects or from specified {@link RevocationValuesType}.
     */
    private void addRevocationData(ValidateRequestType validateRequest, List<byte[]> ocspResponses,
            List<byte[]> crls, RevocationValuesType revocationData) {
        be.fedict.trust.xkms.extensions.ObjectFactory extensionsObjectFactory = new be.fedict.trust.xkms.extensions.ObjectFactory();
        RevocationDataMessageExtensionType revocationDataMessageExtension = extensionsObjectFactory
                .createRevocationDataMessageExtensionType();

        if (null != revocationData) {
            revocationDataMessageExtension.setRevocationValues(revocationData);
        } else {
            be.fedict.trust.client.jaxb.xades132.ObjectFactory xadesObjectFactory = new be.fedict.trust.client.jaxb.xades132.ObjectFactory();
            RevocationValuesType revocationValues = xadesObjectFactory.createRevocationValuesType();

            // OCSP
            OCSPValuesType ocspValues = xadesObjectFactory.createOCSPValuesType();
            for (byte[] ocspResponse : ocspResponses) {
                EncapsulatedPKIDataType ocspValue = xadesObjectFactory.createEncapsulatedPKIDataType();
                ocspValue.setValue(ocspResponse);
                ocspValues.getEncapsulatedOCSPValue().add(ocspValue);
            }
            revocationValues.setOCSPValues(ocspValues);

            // CRL
            CRLValuesType crlValues = xadesObjectFactory.createCRLValuesType();
            for (byte[] crl : crls) {
                EncapsulatedPKIDataType crlValue = xadesObjectFactory.createEncapsulatedPKIDataType();
                crlValue.setValue(crl);
                crlValues.getEncapsulatedCRLValue().add(crlValue);
            }
            revocationValues.setCRLValues(crlValues);
            revocationDataMessageExtension.setRevocationValues(revocationValues);
        }

        validateRequest.getMessageExtension().add(revocationDataMessageExtension);
    }

    /**
     * Add the specified {@link TimeStampToken} to the
     * {@link ValidateRequestType}.
     */
    private void addTimeStampToken(ValidateRequestType validateRequest, TimeStampToken timeStampToken) {
        be.fedict.trust.xkms.extensions.ObjectFactory extensionsObjectFactory = new be.fedict.trust.xkms.extensions.ObjectFactory();
        be.fedict.trust.client.jaxb.xades132.ObjectFactory xadesObjectFactory = new be.fedict.trust.client.jaxb.xades132.ObjectFactory();

        TSAMessageExtensionType tsaMessageExtension = extensionsObjectFactory.createTSAMessageExtensionType();
        EncapsulatedPKIDataType timeStampTokenValue = xadesObjectFactory.createEncapsulatedPKIDataType();
        try {
            timeStampTokenValue.setValue(timeStampToken.getEncoded());
        } catch (IOException e) {
            LOG.error("Failed to get encoded timestamp token", e);
            throw new RuntimeException(e);
        }
        tsaMessageExtension.setEncapsulatedTimeStamp(timeStampTokenValue);
        validateRequest.getMessageExtension().add(tsaMessageExtension);
    }

    /**
     * Add the specified {@link EncapsulatedPKIDataType} holding the attribute
     * certificate to the {@link ValidateRequestType}.
     */
    private void addAttributeCertificates(ValidateRequestType validateRequest,
            CertifiedRolesListType attributeCertificates) {
        be.fedict.trust.xkms.extensions.ObjectFactory extensionsObjectFactory = new be.fedict.trust.xkms.extensions.ObjectFactory();

        AttributeCertificateMessageExtensionType attributeCertificateMessageExtension = extensionsObjectFactory
                .createAttributeCertificateMessageExtensionType();
        attributeCertificateMessageExtension.setCertifiedRoles(attributeCertificates);
        validateRequest.getMessageExtension().add(attributeCertificateMessageExtension);
    }

    /**
     * Checks the ResultMajor and ResultMinor code.
     */
    private void checkResponse(ValidateResultType validateResult) throws TrustDomainNotFoundException {
        if (!validateResult.getResultMajor().equals(ResultMajorCode.SUCCESS.getErrorCode())) {

            if (validateResult.getResultMinor().equals(ResultMinorCode.TRUST_DOMAIN_NOT_FOUND.getErrorCode())) {
                throw new TrustDomainNotFoundException();
            }
        }
    }

    /**
     * Return the optionally filled {@link RevocationValuesType}. Returns
     * <code>null</code> if this was not specified.
     */
    public RevocationValuesType getRevocationValues() {
        return this.revocationValues;
    }

    /**
     * Returns the XKMS v2.0 reason URIs for the failed validation.
     * 
     * @see <a href="http://www.w3.org/TR/xkms2/#XKMS_2_0_Section_5_1">XKMS
     *      2.0</a>
     */
    public List<String> getInvalidReasons() {
        return this.invalidReasonURIs;
    }

    private XMLGregorianCalendar getXmlGregorianCalendar(Date date) {
        try {
            DatatypeFactory datatypeFactory = DatatypeFactory.newInstance();

            GregorianCalendar gregorianCalendar = new GregorianCalendar();
            gregorianCalendar.setTime(date);
            XMLGregorianCalendar currentXmlGregorianCalendar = datatypeFactory
                    .newXMLGregorianCalendar(gregorianCalendar);
            return currentXmlGregorianCalendar;

        } catch (DatatypeConfigurationException e) {
            throw new RuntimeException("datatype config error");
        }
    }
}