xades4j.providers.impl.DefaultTimeStampVerificationProvider.java Source code

Java tutorial

Introduction

Here is the source code for xades4j.providers.impl.DefaultTimeStampVerificationProvider.java

Source

/*
 * XAdES4j - A Java library for generation and verification of XAdES signatures.
 * Copyright (C) 2010 Luis Goncalves.
 *
 * XAdES4j is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 3 of the License, or any later version.
 *
 * XAdES4j 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 XAdES4j. If not, see <http://www.gnu.org/licenses/>.
 */
package xades4j.providers.impl;

import com.google.inject.Inject;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.Provider;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import org.apache.xml.security.algorithms.MessageDigestAlgorithm;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.cms.jcajce.JcaX509CertSelectorConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.tsp.TSPAlgorithms;
import org.bouncycastle.tsp.TSPException;
import org.bouncycastle.tsp.TSPValidationException;
import org.bouncycastle.tsp.TimeStampToken;
import org.bouncycastle.util.Selector;
import xades4j.UnsupportedAlgorithmException;
import xades4j.XAdES4jException;
import xades4j.providers.CertificateValidationProvider;
import xades4j.providers.MessageDigestEngineProvider;
import xades4j.providers.TimeStampTokenDigestException;
import xades4j.providers.TimeStampTokenSignatureException;
import xades4j.providers.TimeStampTokenStructureException;
import xades4j.providers.TimeStampTokenTSACertException;
import xades4j.providers.TimeStampTokenVerificationException;
import xades4j.providers.TimeStampVerificationProvider;
import xades4j.providers.ValidationData;

/**
 * Default implementation of {@code TimeStampVerificationProvider}. It verifies
 * the token signature, including the TSA certificate, and the digest imprint.
 * <p>
 * The implementation is based on Bouncy Castle and <b>only supports DER-encoded tokens</b>.
 * @author Lus
 */
public class DefaultTimeStampVerificationProvider implements TimeStampVerificationProvider {

    private static final Map<ASN1ObjectIdentifier, String> digestOidToUriMappings;

    static {
        digestOidToUriMappings = new HashMap<ASN1ObjectIdentifier, String>(5);
        digestOidToUriMappings.put(TSPAlgorithms.MD5, MessageDigestAlgorithm.ALGO_ID_DIGEST_NOT_RECOMMENDED_MD5);
        digestOidToUriMappings.put(TSPAlgorithms.RIPEMD160, MessageDigestAlgorithm.ALGO_ID_DIGEST_RIPEMD160);
        digestOidToUriMappings.put(TSPAlgorithms.SHA1, MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA1);
        digestOidToUriMappings.put(TSPAlgorithms.SHA256, MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA256);
        digestOidToUriMappings.put(TSPAlgorithms.SHA384, MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA384);
        digestOidToUriMappings.put(TSPAlgorithms.SHA512, MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA512);
    }

    // TODO this probably should be a provider to avoid being dependent on a fixed set of algorithms
    private static String uriForDigest(ASN1ObjectIdentifier digestalgOid) {
        return digestOidToUriMappings.get(digestalgOid);
    }

    private final CertificateValidationProvider certificateValidationProvider;
    private final MessageDigestEngineProvider messageDigestProvider;
    private final JcaSimpleSignerInfoVerifierBuilder signerInfoVerifierBuilder;
    private final JcaX509CertificateConverter x509CertificateConverter;
    private final JcaX509CertSelectorConverter x509CertSelectorConverter;

    @Inject
    public DefaultTimeStampVerificationProvider(CertificateValidationProvider certificateValidationProvider,
            MessageDigestEngineProvider messageDigestProvider) {
        this.certificateValidationProvider = certificateValidationProvider;
        this.messageDigestProvider = messageDigestProvider;

        Provider bcProv = new BouncyCastleProvider();
        this.signerInfoVerifierBuilder = new JcaSimpleSignerInfoVerifierBuilder().setProvider(bcProv);
        this.x509CertificateConverter = new JcaX509CertificateConverter().setProvider(bcProv);
        this.x509CertSelectorConverter = new JcaX509CertSelectorConverter();
    }

    @Override
    public Date verifyToken(byte[] timeStampToken, byte[] tsDigestInput)
            throws TimeStampTokenVerificationException {
        TimeStampToken tsToken;
        try {
            ASN1InputStream asn1is = new ASN1InputStream(timeStampToken);
            ContentInfo tsContentInfo = ContentInfo.getInstance(asn1is.readObject());
            asn1is.close();
            tsToken = new TimeStampToken(tsContentInfo);
        } catch (IOException ex) {
            throw new TimeStampTokenStructureException("Error parsing encoded token", ex);
        } catch (TSPException ex) {
            throw new TimeStampTokenStructureException("Invalid token", ex);
        }

        X509Certificate tsaCert = null;
        try {
            /* Validate the TSA certificate */
            LinkedList<X509Certificate> certs = new LinkedList<X509Certificate>();
            for (Object certHolder : tsToken.getCertificates().getMatches(new AllCertificatesSelector())) {
                certs.add(this.x509CertificateConverter.getCertificate((X509CertificateHolder) certHolder));
            }

            ValidationData vData = this.certificateValidationProvider.validate(
                    x509CertSelectorConverter.getCertSelector(tsToken.getSID()),
                    tsToken.getTimeStampInfo().getGenTime(), certs);

            tsaCert = vData.getCerts().get(0);
        } catch (CertificateException ex) {
            throw new TimeStampTokenVerificationException(ex.getMessage(), ex);
        } catch (XAdES4jException ex) {
            throw new TimeStampTokenTSACertException("cannot validate TSA certificate", ex);
        }

        try {
            tsToken.validate(this.signerInfoVerifierBuilder.build(tsaCert));
        } catch (TSPValidationException ex) {
            throw new TimeStampTokenSignatureException("Invalid token signature or certificate", ex);
        } catch (Exception ex) {
            throw new TimeStampTokenVerificationException("Error when verifying the token signature", ex);
        }

        org.bouncycastle.tsp.TimeStampTokenInfo tsTokenInfo = tsToken.getTimeStampInfo();

        try {
            String digestAlgUri = uriForDigest(tsTokenInfo.getMessageImprintAlgOID());
            MessageDigest md = messageDigestProvider.getEngine(digestAlgUri);

            if (!Arrays.equals(md.digest(tsDigestInput), tsTokenInfo.getMessageImprintDigest())) {
                throw new TimeStampTokenDigestException();
            }
        } catch (UnsupportedAlgorithmException ex) {
            throw new TimeStampTokenVerificationException("The token's digest algorithm is not supported", ex);
        }

        return tsTokenInfo.getGenTime();
    }

    /** Selector selecting all certificates. */
    private static class AllCertificatesSelector implements Selector {

        @Override
        public boolean match(Object o) {
            return true;
        }

        @Override
        public Object clone() {
            return this;
        }

    }
}