de.rub.dez6a3.jpdfsigner.TimeStampToken.java Source code

Java tutorial

Introduction

Here is the source code for de.rub.dez6a3.jpdfsigner.TimeStampToken.java

Source

/*
 * JPDFSigner - Sign PDFs online using smartcards (TimeStampToken.java)
 * Copyright (C) 2013  Ruhr-Universitaet Bochum - Daniel Moczarski, Haiko te Neues
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/gpl.txt>.
 *
 */

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package de.rub.dez6a3.jpdfsigner;

import java.io.IOException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.util.Collection;
import java.util.Date;
import java.security.cert.CertStore;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.text.ParseException;

import org.bouncycastle.cms.CMSProcessable;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.SignerId;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.jce.PrincipalUtil;
import org.bouncycastle.jce.X509Principal;
import org.bouncycastle.asn1.cms.Attribute;
import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.asn1.ess.ESSCertID;
import org.bouncycastle.asn1.ess.SigningCertificate;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.tsp.TSTInfo;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.tsp.Accuracy;
import org.bouncycastle.tsp.GenTimeAccuracy;
import org.bouncycastle.tsp.TSPException;
import org.bouncycastle.tsp.TSPUtil;
import org.bouncycastle.tsp.TSPValidationException;

public class TimeStampToken {

    CMSSignedData tsToken;
    SignerInformation tsaSignerInfo;
    Date genTime;
    TimeStampTokenInfo tstInfo;
    ESSCertID certID;

    TimeStampToken(ContentInfo contentInfo) throws TSPException, IOException {
        this(new CMSSignedData(contentInfo));
    }

    public TimeStampToken(CMSSignedData signedData) throws TSPException, IOException {
        this.tsToken = signedData;

        if (!this.tsToken.getSignedContentTypeOID().equals(PKCSObjectIdentifiers.id_ct_TSTInfo.getId())) {
            throw new TSPValidationException("ContentInfo object not for a time stamp.");
        }

        Collection signers = tsToken.getSignerInfos().getSigners();

        if (signers.size() != 1) {
            throw new IllegalArgumentException("Time-stamp token signed by " + signers.size()
                    + " signers, but it must contain just the TSA signature.");
        }

        tsaSignerInfo = (SignerInformation) signers.iterator().next();

        try {
            CMSProcessable content = tsToken.getSignedContent();
            ByteArrayOutputStream bOut = new ByteArrayOutputStream();

            content.write(bOut);

            ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(bOut.toByteArray()));

            this.tstInfo = new TimeStampTokenInfo(TSTInfo.getInstance(aIn.readObject()));

            Attribute attr = tsaSignerInfo.getSignedAttributes()
                    .get(PKCSObjectIdentifiers.id_aa_signingCertificate);

            if (attr == null) {
                throw new TSPValidationException("no signing certificate attribute found, time stamp invalid.");
            }

            SigningCertificate signCert = SigningCertificate.getInstance(attr.getAttrValues().getObjectAt(0));

            this.certID = ESSCertID.getInstance(signCert.getCerts()[0]);
        } catch (CMSException e) {
            throw new TSPException(e.getMessage(), e.getUnderlyingException());
        }
    }

    public TimeStampTokenInfo getTimeStampInfo() {
        return tstInfo;
    }

    public SignerId getSID() {
        return tsaSignerInfo.getSID();
    }

    public AttributeTable getSignedAttributes() {
        return tsaSignerInfo.getSignedAttributes();
    }

    public AttributeTable getUnsignedAttributes() {
        return tsaSignerInfo.getUnsignedAttributes();
    }

    public CertStore getCertificatesAndCRLs(String type, String provider)
            throws NoSuchAlgorithmException, NoSuchProviderException, CMSException {
        return tsToken.getCertificatesAndCRLs(type, provider);
    }

    /**
     * Validate the time stamp token.
     * <p>
     * To be valid the token must be signed by the passed in certificate and
     * the certificate must be the one refered to by the SigningCertificate
     * attribute included in the hashed attributes of the token. The
     * certifcate must also have the ExtendedKeyUsageExtension with only
     * KeyPurposeId.id_kp_timeStamping and have been valid at the time the
     * timestamp was created.
     * </p>
     * <p>
     * A successful call to validate means all the above are true.
     * </p>
     */
    public void validate(X509Certificate cert, String provider) throws TSPException, TSPValidationException,
            CertificateExpiredException, CertificateNotYetValidException, NoSuchProviderException {
        try {
            if (!MessageDigest.isEqual(certID.getCertHash(),
                    MessageDigest.getInstance("SHA-1").digest(cert.getEncoded()))) {
                throw new TSPValidationException("certificate hash does not match certID hash.");
            }

            if (certID.getIssuerSerial() != null) {
                if (!certID.getIssuerSerial().getSerial().getValue().equals(cert.getSerialNumber())) {
                    throw new TSPValidationException(
                            "certificate serial number does not match certID for signature.");
                }

                GeneralName[] names = certID.getIssuerSerial().getIssuer().getNames();
                X509Principal principal = PrincipalUtil.getIssuerX509Principal(cert);
                boolean found = false;

                for (int i = 0; i != names.length; i++) {
                    if (names[i].getTagNo() == 4
                            && new X509Principal(X509Name.getInstance(names[i].getName())).equals(principal)) {
                        found = true;
                        break;
                    }
                }

                if (!found) {
                    throw new TSPValidationException("certificate name does not match certID for signature. ");
                }
            }

            TSPUtil.validateCertificate(cert);

            cert.checkValidity(tstInfo.getGenTime());

            if (!tsaSignerInfo.verify(cert, provider)) {
                throw new TSPValidationException("signature not created by certificate.");
            }
        } catch (CMSException e) {
            if (e.getUnderlyingException() != null) {
                throw new TSPException(e.getMessage(), e.getUnderlyingException());
            } else {
                throw new TSPException("CMS exception: " + e, e);
            }
        } catch (NoSuchAlgorithmException e) {
            throw new TSPException("cannot find algorithm: " + e, e);
        } catch (CertificateEncodingException e) {
            throw new TSPException("problem processing certificate: " + e, e);
        }
    }

    /**
     * Return the underlying CMSSignedData object.
     *
     * @return the underlying CMS structure.
     */
    public CMSSignedData toCMSSignedData() {
        return tsToken;
    }

    /**
     * Return a ASN.1 encoded byte stream representing the encoded object.
     *
     * @throws IOException if encoding fails.
     */
    public byte[] getEncoded() throws IOException {
        return tsToken.getEncoded();
    }
}

class TimeStampTokenInfo {

    TSTInfo tstInfo;
    Date genTime;

    TimeStampTokenInfo(TSTInfo tstInfo) throws TSPException, IOException {
        this.tstInfo = tstInfo;

        try {
            this.genTime = tstInfo.getGenTime().getDate();
        } catch (ParseException e) {
            throw new TSPException("unable to parse genTime field");
        }
    }

    public boolean isOrdered() {
        return tstInfo.getOrdering().isTrue();
    }

    public Accuracy getAccuracy() {
        return tstInfo.getAccuracy();
    }

    public Date getGenTime() {
        return genTime;
    }

    public GenTimeAccuracy getGenTimeAccuracy() {
        if (this.getAccuracy() != null) {
            return new GenTimeAccuracy(this.getAccuracy());
        }

        return null;
    }

    public String getPolicy() {
        return tstInfo.getPolicy().getId();
    }

    public BigInteger getSerialNumber() {
        return tstInfo.getSerialNumber().getValue();
    }

    /**
     * @return the nonce value, null if there isn't one.
     */
    public BigInteger getNonce() {
        if (tstInfo.getNonce() != null) {
            return tstInfo.getNonce().getValue();
        }

        return null;
    }

    public String getMessageImprintAlgOID() {
        return tstInfo.getMessageImprint().getHashAlgorithm().getObjectId().getId();
    }

    public byte[] getMessageImprintDigest() {
        return tstInfo.getMessageImprint().getHashedMessage();
    }

    public byte[] getEncoded() throws IOException {
        return tstInfo.getEncoded();
    }
}