lucasdc
lucasdc

Reputation: 1042

How to sign a PDF with embedded timestamp and LTV enabled?

I am trying to sign a pdf with timestamp and LTV enabled so that it is shown like this in Adobe Reader:

Correct signing

In english it means that "the signature includes an embedded timestamp" and "the signature is LTV enabled". Here is the code I'm using:

PrivateKey pk = // get pk from an encrypting certificate created using encrypting file system
Certificate[] chain = ks.getCertificateChain(alias);

PdfReader reader = new PdfReader(src);
FileOutputStream fout = new FileOutputStream(dest);
PdfStamper stp = PdfStamper.createSignature(reader, fout, '\0');

PdfSignatureAppearance sap = stp.getSignatureAppearance();

ExternalSignature signature = new PrivateKeySignature(pk, "SHA-512", "SunMSCAPI");
TSAClient tsc = null;
String url = // TSA URL
tsc = new TSAClientBouncyCastle(url, null, null, 4096, "SHA-512");

List<CrlClient> crlList = new ArrayList<>();
crlList.add(new CrlClientOnline(chain));

ExternalDigest digest = new BouncyCastleDigest();
MakeSignature.signDetached(sap, digest, signature, chain, crlList, null, tsc, 0, CryptoStandard.CMS);

Based on this answer, I need a way to get CRL for the TSA Certificate to the CrlList, but.. how can I get the TSA Certificate? Do I need to make a timestamp-query request to the TSA and read response and then add it to CrlList? Note that this is already done inside MakeSignature.signDetached when it calls sgn.getEncodedPKCS7. Note that I am using a free TSA server.

This is what is shown in Adobe Reader with the code above. Signature details: enter image description here

Timestamp details: enter image description here

TSA Certificate details: enter image description here


UPDATE

Since it was a free TSA server, I just had to add the TSA server certificate in Adobe Trusted Certificates and now it works. But, I have made another test using a Smart Card to sign a document and here is what I got (I've added the root cert to the Trusted Certificates in Adobe):

Signature details:

enter image description here

Signing certificate details:

enter image description here

Based on this link, LTV enabled means that all information necessary to validate the file (minus root certs) is contained within the PDF. So, the PDF is LTV enabled if it is signed correctly and contains all necessary certificates and a valid CRL or OSCP response for every certificate, and also, if it includes signatures over CRLs and OCSPs, not just the signature certificate. It looks like I got all of those requirements or am I missing something? If so, how can I know what is missing to get a LTV enabled pdf?

Upvotes: 5

Views: 4202

Answers (1)

lucasdc
lucasdc

Reputation: 1042

First, based on @mkl comment, I added the TSA server certificate to the Adobe Trusted Certificates so that the message

the signature includes an embedded timestamp but it could not be verified

became

the signature includes an embedded timestamp

And to solve

Signature is not LTV enabled and will expire after (...)

I could note that using

List<CrlClient> crlList = new ArrayList<>();
crlList.add(new CrlClientOnline(chain));

there were some CRL's (some of the certificates had more than one distribution point) not being added - making the pdf LTV not enabled. To solve it I've done this way:

// long term validation (LTV)
List<CrlClient> crlList = new ArrayList<>();

for(Certificate cert : chain) {
    X509Certificate c = (X509Certificate)cert;
    List<String> crls = this.getCrlDistributionPoints(c);
    if(crls != null && !crls.isEmpty()) {
        crlList.add(new CrlClientOnline(crls.toArray(new String[crls.size()])));
    }
}

private List<String> getCrlDistributionPoints(final X509Certificate cert) throws Exception {
    final byte[] crldpExt = cert.getExtensionValue(X509Extension.cRLDistributionPoints.getId());
    if (crldpExt == null) {
        final List<String> emptyList = new ArrayList<String>();
        return emptyList;
    }
    ASN1InputStream oAsnInStream = null;
    ASN1InputStream oAsnInStream2 = null;
    List<String> crlUrls = new ArrayList<String>();

    try { 
        oAsnInStream = new ASN1InputStream(new ByteArrayInputStream(crldpExt));
        final ASN1Object derObjCrlDP = oAsnInStream.readObject();
        final DEROctetString dosCrlDP = (DEROctetString) derObjCrlDP;
        final byte[] crldpExtOctets = dosCrlDP.getOctets();
        oAsnInStream2 = new ASN1InputStream(new ByteArrayInputStream(crldpExtOctets));
        final ASN1Object derObj2 = oAsnInStream2.readObject();
        final CRLDistPoint distPoint = CRLDistPoint.getInstance(derObj2);
        for (final DistributionPoint dp : distPoint.getDistributionPoints()) {
            final DistributionPointName dpn = dp.getDistributionPoint();
            // Look for URIs in fullName
            if (dpn != null) {
                if (dpn.getType() == DistributionPointName.FULL_NAME) {
                    final GeneralName[] genNames = GeneralNames.getInstance(dpn.getName()).getNames();
                    // Look for an URI
                    for (int j = 0; j < genNames.length; j++) {
                        if (genNames[j].getTagNo() == GeneralName.uniformResourceIdentifier) {
                            final String url = DERIA5String.getInstance(genNames[j].getName()).getString();
                            crlUrls.add(url);
                        }
                    }
                }
            }
        }
    } catch(IOException e) {
        throw new Exception(e.getMessage(), e);
    } finally {
        IOUtils.closeQuietly(oAsnInStream);
        IOUtils.closeQuietly(oAsnInStream2);
    }

    return crlUrls;
}

Upvotes: 1

Related Questions