5
votes

I'm using iText 5.5.3 to sign PDF documents. I need these documents to be timestamped and LTV-enabled. I followed the instructions and used the addLtv method (code sample 5.9, page 137 in the Lowagie's white paper). I get a PDF with 2 signatures, which is normal: the first is my own signature, the second is the document-level timestamp.

However, Acrobat tells me my signature is LTV enabled, but the timestamp signature is not :

Image from Acrobat Pro XI http://img15.hostingpics.net/pics/727285so2.jpg

This is because the revocation info of the timestamp certificate is not embedded in the document :

Missing revocation info 1 http://img15.hostingpics.net/pics/491507so2a.jpg

Missing revocation info 2 http://img15.hostingpics.net/pics/312720so2b.jpg

From my understanding, the addLtv method should get all revocation information needed and embed it in the document. Is that correct, or do I have to "manually" get and embed these informations ?

2
As you can see in the sample code, it first adds validation related information for the signatures existing in the document and only thereafter adds a time stamp. Thus, obviously, it can only by chance contain validation information applicable to the time stamp.mkl
I thought the "if (pkcs7.isTsp())" section was intented to add the timestamp validation info. Seems my coding skills stop here... Is there a code snippet somewhere which I can use to add the missing info?Silas
I thought the "if (pkcs7.isTsp())" section was intented to add the timestamp validation info - it is... for an already existing timestamp! ... Is there a code snippet somewhere which I can use to add the missing info - Run the sample a second time without adding another timestamp, i.e. without the final LtvTimestamp.timestamp(sap, tsa, null); line.mkl

2 Answers

11
votes

This is the sample code this question is about:

public void addLtv(String src, String dest, OcspClient ocsp, CrlClient crl, TSAClient tsa) throws IOException, DocumentException, GeneralSecurityException
{
    PdfReader r = new PdfReader(src);
    FileOutputStream fos = new FileOutputStream(dest);
    PdfStamper stp = PdfStamper.createSignature(r, fos, '\0', null, true);
    LtvVerification v = stp.getLtvVerification();
    AcroFields fields = stp.getAcroFields();
    List<String> names = fields.getSignatureNames();
    String sigName = names.get(names.size() - 1);
    PdfPKCS7 pkcs7 = fields.verifySignature(sigName);
    if (pkcs7.isTsp())
    {
        v.addVerification(sigName, ocsp, crl,
            LtvVerification.CertificateOption.SIGNING_CERTIFICATE,
            LtvVerification.Level.OCSP_CRL,
            LtvVerification.CertificateInclusion.NO);
    }
    else
    {
        for (String name : names)
        {
            v.addVerification(name, ocsp, crl,
                LtvVerification.CertificateOption.WHOLE_CHAIN,
                LtvVerification.Level.OCSP_CRL,
                LtvVerification.CertificateInclusion.NO);
        }
    }
    PdfSignatureAppearance sap = stp.getSignatureAppearance();
    LtvTimestamp.timestamp(sap, tsa, null);
}

This code identifies the most recently filled signature field of the PDF and checks whether it is a document time stamp or an usual signature.

If it is a document time stamp, the code adds validation information only for this document timestamp. Otherwise the code adds validation information for all signatures.

(The assumed work flow behind this is that the document is signed (for certification and/or approval) a number of times first, and then the document enters LTV cycles adding validation information and document time stamps but no usual signatures anymore. Your work flow may vary and, therefore, your program logic, too.)

Only after all this is done, a new document time stamp is added.

For this finally added time stamp no validation information are explicitly added to the PDF (if document time stamps from the same TSA have been applied in short succession, validation information included for a prior time stamp may be applicable). And this is why Adobe Reader/Acrobat usually does not consider this document time stamp LTV enabled.

If you need validation information for this final document time stamp, too, simply apply this method (the same as the method above, merely not adding a document time stamp) to the file with the document time stamp:

public void addLtvNoTS(String src, String dest, OcspClient ocsp, CrlClient crl) throws IOException, DocumentException, GeneralSecurityException
{
    PdfReader r = new PdfReader(src);
    FileOutputStream fos = new FileOutputStream(dest);
    PdfStamper stp = new PdfStamper(r, fos, '\0', true);
    LtvVerification v = stp.getLtvVerification();
    AcroFields fields = stp.getAcroFields();
    List<String> names = fields.getSignatureNames();
    String sigName = names.get(names.size() - 1);
    PdfPKCS7 pkcs7 = fields.verifySignature(sigName);
    if (pkcs7.isTsp())
    {
        v.addVerification(sigName, ocsp, crl,
            LtvVerification.CertificateOption.SIGNING_CERTIFICATE,
            LtvVerification.Level.OCSP_CRL,
            LtvVerification.CertificateInclusion.NO);
    }
    else
    {
        for (String name : names)
        {
            v.addVerification(name, ocsp, crl,
                LtvVerification.CertificateOption.WHOLE_CHAIN,
                LtvVerification.Level.OCSP_CRL,
                LtvVerification.CertificateInclusion.NO);
        }
    }
    stp.close();
}

Background

The reason why the iText addLtv example does not (necessarily) create LTV-enabled PDFs is that it is nearer to the best practices for LTV as proposed by ETSI in the PAdES specification than to Adobe's best practices for LTV.

According to ETSI TS 102 778-4 V1.1.2 (2009-12) the structure of a PDF document to which LTV is applied is illustrated in figure 2.

Figure 2: Illustration of PDF Document with LTV

The life-time of the protection can be further extended beyond the life-of the last document Time-stamp applied by adding further DSS information to validate the previous last document Time-stamp along with a new document Time-stamp. This is illustrated in figure 3.

Figure 3: Illustration of PDF Document with repeated LTV

On the other hand, according to Adobe (as written by their PDF evangelist Leonard Rosenthol on the iText mailing list in January 2013),

LTV enabled means that all information necessary to validate the file (minus root certs) is contained within. So this statement of yours would be true.

the PDF is signed correctly and contains all necessary certificates, a valid CRL or OSCP response for every certificate

But since the only way for that statement to be true is for the presence of DSS, you must have DSS for LTV-enabled to appear. No timestamp (regular or document level) is required.

Due to this divergence PDF documents with LTV according to ETSI usually are presented by Adobe software to have one not LTV-enabled document time stamp.

See also

0
votes

What I did was to embed the LTV data for the timestamp before timestamping the document by requesting two timestamps (using the first one to extract LTV data and update DSS and the second one to actually timestamp the document):

  1. Request a dummy timestamp token from the TSA
  2. Extract and validate the trust-chain of this token
  3. Add OSCP replies and CRLs for the certificates in the chain to the document DSS
  4. Now request second timestamp for the document (including the updated DSS) and use it to timestamp the PDF
  5. Verify that the two timestamps were signed by the same certificate (for the unlikely case that the TSA used different certificates)

Extracting the signing certificate from the tsa token:

        IDigest messageDigest = tsaClient.GetMessageDigest();
        byte[] tsImprint = new byte[messageDigest.GetDigestSize()];
        messageDigest.DoFinal(tsImprint, 0);
        byte[] tsToken;
        try {
            tsToken = tsaClient.GetTimeStampToken(tsImprint);
        } catch(Exception e) {
            throw new GeneralSecurityException(e.Message);
        }
        Asn1Sequence asn1Seq = Asn1Sequence.GetInstance(tsToken);
        ContentInfo sigData = ContentInfo.GetInstance(asn1Seq);
        TimeStampToken token = new TimeStampToken(sigData);
        IX509Store tokenCerts = token.GetCertificates("COLLECTION");
        List<X509Certificate> signingCerts = new List<X509Certificate>();
        foreach(X509Certificate cert in tokenCerts.GetMatches(token.SignerID)) {
            signingCerts.Add(cert);
        }
        // now perform LTV steps for signingCerts[0] ...

TSA LTV