/*
* DSS - Digital Signature Services
*
* Copyright (C) 2013 European Commission, Directorate-General Internal Market and Services (DG MARKT), B-1049 Bruxelles/Brussel
*
* Developed by: 2013 ARHS Developments S.A. (rue Nicolas Bové 2B, L-1253 Luxembourg) http://www.arhs-developments.com
*
* This file is part of the "DSS - Digital Signature Services" project.
*
* "DSS - Digital Signature Services" 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 2.1 of the
* License, or (at your option) any later version.
*
* DSS 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
* "DSS - Digital Signature Services". If not, see <http://www.gnu.org/licenses/>.
*/
package eu.europa.ec.markt.dss.validation102853;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.Certificate;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cms.SignerInformationVerifier;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.tsp.TSPException;
import org.bouncycastle.tsp.TSPValidationException;
import org.bouncycastle.tsp.TimeStampToken;
import org.bouncycastle.tsp.TimeStampTokenInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import eu.europa.ec.markt.dss.DSSUtils;
import eu.europa.ec.markt.dss.DigestAlgorithm;
import eu.europa.ec.markt.dss.EncryptionAlgorithm;
import eu.europa.ec.markt.dss.SignatureAlgorithm;
import eu.europa.ec.markt.dss.exception.DSSException;
/**
* SignedToken containing a TimeStamp.
*
* @version $Revision: 1824 $ - $Date: 2013-03-28 15:57:23 +0100 (Thu, 28 Mar 2013) $
*/
public class TimestampToken extends Token {
private static final Logger LOG = LoggerFactory.getLogger(TimestampToken.class);
private final TimeStampToken timeStamp;
private TimestampType timeStampType;
private int dssId;
private CAdESCertificateSource wrappedSource;
private X500Principal issuerX500Principal;
private boolean messageImprintData;
private Boolean messageImprintIntact = null;
private String signedDataMessage = "";
private List<TimestampReference> timestampedReferences;
private List<TimestampInclude> timestampIncludes;
/**
* Defines for archive timestamp its type.
*/
private ArchiveTimestampType archiveTimestampType;
/**
* This attribute is used for XAdES timestamps. It indicates the canonicalization method used before creating the digest.
*/
private String canonicalizationMethod;
/**
* This attribute is used only with XAdES timestamps. It represents the hash code of the DOM element containing the timestamp. It's an internal attribute which allows to
* unambiguously identify a timestamp.
*/
private int hashCode;
/**
* Constructor with an indication of the timestamp type. The default constructor for {@code TimestampToken}.
*
* @param timeStamp {@code TimeStampToken}
* @param type {@code TimestampType}
* @param certPool {@code CertificatePool} which is used to identify the signing certificate of the timestamp
*/
public TimestampToken(final TimeStampToken timeStamp, final TimestampType type, final CertificatePool certPool) {
this.timeStamp = timeStamp;
this.timeStampType = type;
this.extraInfo = new TokenValidationExtraInfo();
wrappedSource = new CAdESCertificateSource(timeStamp, certPool);
final Collection<CertificateToken> certs = wrappedSource.getCertificates();
for (final CertificateToken certificateToken : certs) {
final byte[] encoded = certificateToken.getEncoded();
final Certificate certificate = Certificate.getInstance(encoded);
final X509CertificateHolder x509CertificateHolder = new X509CertificateHolder(certificate);
if (timeStamp.getSID().match(x509CertificateHolder)) {
boolean valid = isSignedBy(certificateToken);
if (valid) {
break;
}
}
}
final byte[] digest = DSSUtils.digest(DigestAlgorithm.MD5, DSSUtils.getEncoded(timeStamp));
final String base64Encoded = DSSUtils.base64Encode(digest);
dssId = base64Encoded.hashCode();
}
@Override
public int getDSSId() {
return dssId;
}
@Override
public String getAbbreviation() {
return timeStampType.name() + ": " + getDSSId() + ": " + DSSUtils.formatInternal(timeStamp.getTimeStampInfo().getGenTime());
}
/**
* This method returns the issuing certificate's distinguished subject name.
*
* @return {@code X500Principal} representing the issuing certificate's distinguished subject name.
*/
public X500Principal getIssuerX500Principal() {
return issuerX500Principal;
}
@Override
public boolean isSignedBy(final CertificateToken issuerToken) {
if (this.issuerToken != null) {
return this.issuerToken.equals(issuerToken);
}
final TimestampValidation timestampValidation = validateTimestampToken(timeStamp, issuerToken);
final TimestampValidity timestampValidity = timestampValidation.getValidity();
signatureInvalidityReason = timestampValidity.name();
signatureValid = timestampValidation.isValid();
if (signatureValid) {
this.issuerToken = issuerToken;
issuerX500Principal = issuerToken.getSubjectX500Principal();
final String algorithm = issuerToken.getPublicKey().getAlgorithm();
final EncryptionAlgorithm encryptionAlgorithm = EncryptionAlgorithm.forName(algorithm);
final AlgorithmIdentifier hashAlgorithm = timeStamp.getTimeStampInfo().getHashAlgorithm();
final DigestAlgorithm digestAlgorithm = DigestAlgorithm.forOID(hashAlgorithm.getAlgorithm());
algorithmUsedToSignToken = SignatureAlgorithm.getAlgorithm(encryptionAlgorithm, digestAlgorithm);
}
return signatureValid;
}
private TimestampValidation validateTimestampToken(final TimeStampToken timeStampToken, final CertificateToken issuerToken) {
TimestampValidity timestampValidity;
try {
final JcaSimpleSignerInfoVerifierBuilder verifierBuilder = new JcaSimpleSignerInfoVerifierBuilder();
final X509Certificate x509Certificate = issuerToken.getCertificate();
final SignerInformationVerifier verifier = verifierBuilder.build(x509Certificate);
timeStampToken.validate(verifier);
timestampValidity = TimestampValidity.VALID;
} catch (IllegalArgumentException e) {
timestampValidity = TimestampValidity.NO_SIGNING_CERTIFICATE;
LOG.error("No signing certificate for timestamp token: " + e);
} catch (TSPValidationException e) {
timestampValidity = TimestampValidity.NOT_VALID_SIGNATURE;
} catch (TSPException e) {
timestampValidity = TimestampValidity.NOT_VALID_STRUCTURE;
} catch (OperatorCreationException e) {
timestampValidity = TimestampValidity.NOT_VALID_STRUCTURE;
}
final TimestampValidation timestampValidation = new TimestampValidation(timestampValidity);
return timestampValidation;
}
/**
* Checks if the {@code TimeStampToken} matches the signed data.
*
* @param data the array of {@code byte} representing the timestamped data
* @return true if the data is verified by the TimeStampToken
*/
public boolean matchData(final byte[] data) {
try {
messageImprintData = data != null;
final TimeStampTokenInfo timeStampInfo = timeStamp.getTimeStampInfo();
final ASN1ObjectIdentifier hashAlgorithm = timeStampInfo.getHashAlgorithm().getAlgorithm();
final DigestAlgorithm digestAlgorithm = DigestAlgorithm.forOID(hashAlgorithm);
final byte[] computedDigest = DSSUtils.digest(digestAlgorithm, data);
final byte[] timestampDigest = timeStampInfo.getMessageImprintDigest();
messageImprintIntact = Arrays.equals(computedDigest, timestampDigest);
if (!messageImprintIntact) {
LOG.error("Extracted data from the document: {}", DSSUtils.encodeHexString(data, 200));
LOG.error("Computed digest ({}) on the extracted data from the document : {}", digestAlgorithm, DSSUtils.encodeHexString(computedDigest));
LOG.error("Digest present in TimestampToken: {}", DSSUtils.encodeHexString(timestampDigest));
LOG.error("Digest in TimestampToken matches digest of extracted data from document: {}", messageImprintIntact);
}
} catch (DSSException e) {
messageImprintIntact = false;
signedDataMessage = "Timestamp digest problem: " + e.getMessage();
}
return messageImprintIntact;
}
/**
* Retrieves the type of the timestamp token.
*
* @return {@code TimestampType}
*/
public TimestampType getTimeStampType() {
return timeStampType;
}
/**
* Retrieves the timestamp generation time.
*
* @return {@code Date}
*/
public Date getGenerationTime() {
return timeStamp.getTimeStampInfo().getGenTime();
}
/**
* Retrieves the {@code DigestAlgorithm} used to generate the digest value to timestamp.
*
* @return {@code DigestAlgorithm}
*/
public DigestAlgorithm getSignedDataDigestAlgo() {
final ASN1ObjectIdentifier oid = timeStamp.getTimeStampInfo().getHashAlgorithm().getAlgorithm();
return DigestAlgorithm.forOID(oid);
}
/**
* Retrieves the encoded signed data digest value.
*
* @return base 64 encoded {@code String}
*/
public String getEncodedSignedDataDigestValue() {
final byte[] messageImprintDigest = timeStamp.getTimeStampInfo().getMessageImprintDigest();
return DSSUtils.base64Encode(messageImprintDigest);
}
/**
* @return true if the message imprint data was found, false otherwise
*/
public Boolean isMessageImprintDataFound() {
return messageImprintData;
}
/**
* The method {@code matchData} must be invoked previously.
*
* @return true if the message imprint data is intact, false otherwise
*/
public Boolean isMessageImprintDataIntact() {
if (messageImprintIntact == null) {
throw new DSSException("Invoke matchData(byte[] data) method before!");
}
return messageImprintIntact;
}
/**
* @return {@code List} of {@code TimestampReference}s
*/
public List<TimestampReference> getTimestampedReferences() {
return timestampedReferences;
}
/**
* This method is used to set the timestamped references. The reference can be the digest value of the certificate or of the revocation data. The same references can be
* timestamped by different timestamps.
*
* @param timestampedReferences {@code List} of {@code TimestampReference}
*/
public void setTimestampedReferences(final List<TimestampReference> timestampedReferences) {
this.timestampedReferences = timestampedReferences;
}
/**
* @return {@code ArchiveTimestampType} in the case of an archive timestamp, {@code null} otherwise
*/
public ArchiveTimestampType getArchiveTimestampType() {
return archiveTimestampType;
}
/**
* Archive timestamps can be of different sub type.
*
* @param archiveTimestampType {@code ArchiveTimestampType}
*/
public void setArchiveTimestampType(final ArchiveTimestampType archiveTimestampType) {
this.archiveTimestampType = archiveTimestampType;
}
/**
* Applies only fro XAdES timestamps
*
* @return {@code String} representing the canonicalization method used by the timestamp
*/
public String getCanonicalizationMethod() {
return canonicalizationMethod;
}
/**
* Allows to set the canonicalization method used by the timestamp. Applies only with XAdES timestamps.
*
* @param canonicalizationMethod {@code String} representing the canonicalization method
*/
public void setCanonicalizationMethod(final String canonicalizationMethod) {
this.canonicalizationMethod = canonicalizationMethod;
}
@Override
public byte[] getEncoded() {
return DSSUtils.getEncoded(timeStamp);
}
// TODO-Vin (12/09/2014): Comment!
public List<TimestampInclude> getTimestampIncludes() {
return timestampIncludes;
}
// TODO-Vin (12/09/2014): Comment!
public void setTimestampIncludes(List<TimestampInclude> timestampIncludes) {
this.timestampIncludes = timestampIncludes;
}
/**
* Returns the list of wrapped certificates.
*
* @return {@code List} of {@code CertificateToken}
*/
public List<CertificateToken> getCertificates() {
return wrappedSource.getCertificates();
}
public AttributeTable getUnsignedAttributes() {
return timeStamp.getUnsignedAttributes();
}
/**
* Used only with XAdES timestamps.
*
* @param hashCode the hash code of the DOM element containing the timestamp
*/
public void setHashCode(final int hashCode) {
this.hashCode = hashCode;
}
/**
* Used only with XAdES timestamps.
*
* @return the hash code of the DOM element containing the timestamp
*/
public int getHashCode() {
return hashCode;
}
@Override
public String toString(String indentStr) {
try {
final StringBuffer out = new StringBuffer();
out.append(indentStr).append("TimestampToken[signedBy=").append(issuerToken == null ? "?" : issuerToken.getDSSIdAsString());
out.append(", generated: ").append(DSSUtils.formatInternal(timeStamp.getTimeStampInfo().getGenTime()));
out.append(" / ").append(timeStampType).append('\n');
if (signatureValid) {
indentStr += "\t";
out.append(indentStr).append("Timestamp's signature validity: VALID").append('\n');
indentStr = indentStr.substring(1);
} else {
if (!signatureInvalidityReason.isEmpty()) {
indentStr += "\t";
out.append(indentStr).append("Timestamp's signature validity: INVALID").append(" - ").append(signatureInvalidityReason).append('\n');
indentStr = indentStr.substring(1);
}
}
indentStr += "\t";
if (messageImprintIntact != null) {
if (messageImprintIntact) {
out.append(indentStr).append("Timestamp MATCHES the signed data.").append('\n');
} else {
out.append(indentStr).append("Timestamp DOES NOT MATCH the signed data.").append('\n');
if (!signedDataMessage.isEmpty()) {
out.append(indentStr).append("- ").append(signedDataMessage).append('\n');
}
}
}
indentStr = indentStr.substring(1);
if (issuerToken != null) {
indentStr += "\t";
out.append(issuerToken.toString(indentStr)).append('\n');
indentStr = indentStr.substring(1);
out.append(indentStr);
}
out.append("]");
return out.toString();
} catch (Exception e) {
return getClass().getName();
}
}
}