Question

After a jar is signed and the -tsa option was used, how can I validate that the time stamp was included? I tried:

jarsigner -verify -verbose -certs myApp.jar

But the output does not specify anything about the time stamp. I'm asking because even if I have a typo in the -tsa URL path, the jarsigner succeeds. This is the GlobalSign TSA URL: http://timestamp.globalsign.com/scripts/timstamp.dll and the server behind it apparently accepts any path (ie. timestamp.globalsign.com/foobar), so in the end I'm not really sure my jar is time stamped or not.

Was it helpful?

Solution

Just spent the last 2 hours looking for this issue and finally found a way to identify whether a jar file actually has time stamp information in the Signature Block file included. I could see the GlobalSign certifcate in the hexeditor of the /META-INF/FOO.DSA file, but I did not find any tool which would print out the information you need.

You can rename the FOO.DSA file to foo.p7b to open it in the Windows CertMgr, but it does also not show any time stamp information. I also did not manage to use OpenSSL to verify the DSA file (It's PKCS#7 file format).

So I came up with the following code which will show the Time Stamp SignerInfo and the date when the Timestamp was created. I hope it is a good start for you. You need bcprov-jdk16-144.jar, bctsp-jdk16-144.jar and bcmail-jdk16-144.jar in the classpath. Get them from Bouncycastle

package de.mhaller.bouncycastle;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.Security;
import java.util.Collection;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import org.bouncycastle.asn1.DEREncodable;
import org.bouncycastle.asn1.cms.Attribute;
import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.SignerId;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.tsp.TSPException;
import org.bouncycastle.tsp.TimeStampToken;
import org.bouncycastle.tsp.TimeStampTokenInfo;

public class VerifyTimestampSignature {

    private static boolean found;

    public static void main(String[] args) throws Exception {
        if (args == null || args.length != 1) {
            System.out.println("usage: java " + VerifyTimestampSignature.class.getName()
                    + " [jar-file|dsa-file]");
            return;
        }

        BouncyCastleProvider provider = new BouncyCastleProvider();
        Security.addProvider(provider);

        String filename = args[0];

        if (filename.toLowerCase().endsWith(".dsa")) {
            InputStream dsa = new FileInputStream(filename);
            printDSAInfos(filename, dsa);
            return;
        }

        if (filename.toLowerCase().endsWith(".jar")) {
            InputStream jar = new FileInputStream(filename);
            JarInputStream jarInputStream = new JarInputStream(jar);
            JarEntry nextJarEntry;
            do {
                nextJarEntry = jarInputStream.getNextJarEntry();
                if (nextJarEntry == null) {
                    break;
                }
                if (nextJarEntry.getName().toLowerCase().endsWith(".dsa")) {
                    printDSAInfos(nextJarEntry.getName(), jarInputStream);
                }
            } while (nextJarEntry != null);
        }

        if (!found) {
            System.out.println("No certificate with time stamp information found in " + filename);
        } else {
            System.out.println("Found at least one time stamp info");
            System.out.println("Note: But it was NOT verified for validity!");
        }
    }

    private static void printDSAInfos(String file, InputStream dsa) throws CMSException,
            IOException, TSPException {
        System.out.println("Retrieving time stamp token from: " + file);
        CMSSignedData signature = new CMSSignedData(dsa);
        SignerInformationStore store = signature.getSignerInfos();
        Collection<?> signers = store.getSigners();
        for (Object object : signers) {
            SignerInformation signerInform = (SignerInformation) object;
            AttributeTable attrs = signerInform.getUnsignedAttributes();
            if (attrs == null) {
                System.err
                        .println("Signer Information does not contain any unsigned attributes. A signed jar file with Timestamp information should contain unsigned attributes.");
                continue;
            }
            Attribute attribute = attrs.get(PKCSObjectIdentifiers.id_aa_signatureTimeStampToken);
            DEREncodable dob = attribute.getAttrValues().getObjectAt(0);
            CMSSignedData signedData = new CMSSignedData(dob.getDERObject().getEncoded());
            TimeStampToken tst = new TimeStampToken(signedData);

            SignerId signerId = tst.getSID();
            System.out.println("Signer: " + signerId.toString());

            TimeStampTokenInfo tstInfo = tst.getTimeStampInfo();
            System.out.println("Timestamp generated: " + tstInfo.getGenTime());
            found = true;
        }
    }
}

OTHER TIPS

From https://blogs.oracle.com/mullan/entry/how_to_determine_if_a:

You can use the jarsigner utility to determine if a signed JAR has been timestamped as follows:

jarsigner -verify -verbose -certs signed.jar

where signed.jar is the name of your signed JAR. If it is timestamped, the output will include lines of the following indicating the time it was signed:

[entry was signed on 8/2/13 3:48 PM]

If the JAR is not timestamped, the output will not include those lines.

Java's keytool can confirm whether a signed JAR is timestamped, and can also display the TSA's certificate:

$ keytool -printcert -jarfile myApp.jar

...

Timestamp:

Owner: CN=GeoTrust Timestamping Signer 1, O=GeoTrust Inc, C=US
Issuer: CN=Thawte Timestamping CA, OU=Thawte Certification, O=Thawte, L=Durbanville, ST=Western Cape, C=ZA
Serial number: 5e8d2daca44665546bb587978191a8bf
Valid from: Wed Oct 31 00:00:00 GMT 2007 until: Mon Oct 30 23:59:59 GMT 2017
Certificate fingerprints:
     MD5:  E5:30:07:8E:91:8D:A0:6C:18:6D:91:2A:B6:D2:3A:56
     SHA1: 22:3C:DA:27:07:96:73:81:6B:60:8A:1B:8C:B0:AB:02:30:10:7F:CC
     SHA256: D7:B8:44:BD:39:5A:17:36:02:39:51:C6:4D:6C:81:65:45:93:AD:29:1D:DC:E4:6C:8D:79:B6:65:DF:31:0C:F6
     Signature algorithm name: SHA1withRSA
     Version: 3

...

mhaller provides great code (printDSAInfos). Helps me greatly in my work. However a couple of changes required. DEREncodable class is now changed to ASN1Encodable and getDERObject() method are changed to toASN1Primitive. So the code look like this

    ASN1Encodable dob = attribute.getAttrValues().getObjectAt(0);
    CMSSignedData signedData = new CMSSignedData(dob.toASN1Primitive().getEncoded());
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top