Reputation: 1042
I am trying to sign a pdf with timestamp and LTV enabled so that it is shown like this in Adobe Reader:
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:
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:
Signing certificate details:
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
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