/*
* 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 Luís
*/
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;
}
}
}