Digital signature with timestamp in Java

2019-02-11 08:03发布

问题:

I have an issue creating a valid CMS signature with Bouncy Castle using a trusted timestamp. The signature creation works well (I want to include the signature to a PDF file), the signature is valid. But after I include a trusted timestamp to the signature's unsigned attribute table, the signature still stays valid, but the Reader reports that The signature includes an embedded timestamp but it is invalid. This leads me to believe, that the hash I timestamp is not the correct one, but I cannot seem to figure out what is the problem with it.

Signing code:

Store store = new JcaCertStore(Arrays.asList(certContainer.getChain()));

CMSSignedDataGenerator signedDataGenerator = new CMSSignedDataGenerator();
JcaSignerInfoGeneratorBuilder infoGeneratorBuilder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build());
JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA1withRSA");
signedDataGenerator.addSignerInfoGenerator(
                       infoGeneratorBuilder.build(contentSignerBuilder.build(certContainer.getPrivateKey()), (X509Certificate)certContainer.getSignatureCertificate()));
signedDataGenerator.addCertificates(store);
CMSTypedData cmsData = new CMSProcessableByteArray(data);
signedData = signedDataGenerator.generate(cmsData, false);
Collection<SignerInformation> ss = signedData.getSignerInfos().getSigners();
SignerInformation si = ss.iterator().next(); // get first signer (should be only one)
ASN1EncodableVector timestampVector = new ASN1EncodableVector();
Attribute token = createTSToken(si.getSignature());
timestampVector.add(token);
AttributeTable at = new AttributeTable(timestampVector);
si = SignerInformation.replaceUnsignedAttributes(si, at);
ss.clear();
ss.add(si);
SignerInformationStore newSignerStore = new SignerInformationStore(ss);
CMSSignedData newSignedData = CMSSignedData.replaceSigners(signedData, newSignerStore);

The createTSToken code:

public Attribute createTSToken(byte[] data) throws NoSuchProviderException, NoSuchAlgorithmException, IOException {
    // Generate timestamp
    MessageDigest digest = MessageDigest.getInstance("SHA1", "BC");
    TimeStampResponse response = timestampData(digest.digest(data));
    TimeStampToken timestampToken = response.getTimeStampToken();
    // Create timestamp attribute

    Attribute a = new Attribute(PKCSObjectIdentifiers.id_aa_signatureTimeStampToken, new DERSet(ASN1Primitive.fromByteArray(timestampToken.getEncoded())));
    return a;
}

timestampData:

TimeStampRequestGenerator reqgen = new TimeStampRequestGenerator();
TimeStampRequest req = reqgen.generate(TSPAlgorithms.SHA1, data);
byte request[] = req.getEncoded();

URL url = new URL("http://time.certum.pl");
HttpURLConnection con = (HttpURLConnection) url.openConnection();

con.setDoOutput(true);
con.setDoInput(true);
con.setRequestMethod("POST");
con.setRequestProperty("Content-type", "application/timestamp-query");
con.setRequestProperty("Content-length", String.valueOf(request.length));

OutputStream out = con.getOutputStream();
out.write(request);
out.flush();

if (con.getResponseCode() != HttpURLConnection.HTTP_OK) {
    throw new IOException("Received HTTP error: " + con.getResponseCode() + " - " +                 con.getResponseMessage());
}
InputStream in = con.getInputStream();
TimeStampResp resp = TimeStampResp.getInstance(new ASN1InputStream(in).readObject());
response = new TimeStampResponse(resp);
response.validate(req);
if(response.getStatus() != 0) {
    System.out.println(response.getStatusString());
    return null;
}
return response;

Thanks for your help!

Example files:

Signed PDF

Unsigned PDF

Signed PDF with iText

Signed PDF with LTV - edited

回答1:

signed_lipsum.pdf, first version

The time stamp token references as signer some

CN=e-Szigno Test TSA2,OU=e-Szigno CA,O=Microsec Ltd.,L=Budapest,C=HU

which has been issued by

CN=Microsec e-Szigno Test Root CA 2008,OU=e-Szigno CA,O=Microsec Ltd.,L=Budapest,C=HU

with serial number 7.

It does not provide this certificate itself, though, and neither is it provided by the encapsulating signature CMS container nor in some validation related information PDF document section.

Thus, at least on my computer there is no chance of verifying the time stamp token in any way and Adobe Reader is completely right not to accept the time stamp.

Have you provided the certificate in question on your computer in a way appropriate for your Adobe Reader? If you have and it still does not work, please supply it for further tests. If you have not, try to retrieve and provide them.

You might want to beef up the time stamp token itself to include that certificate before including it into the signature.

signed_lipsum.pdf, second version

In the updated file signed_lipsum.pdf the signature time stamp contains a TSA certificate, but it is the wrong one!

Just like in the first version the time stamp references a signer certificate with

  • Subject CN=e-Szigno Test TSA2,OU=e-Szigno CA,O=Microsec Ltd.,L=Budapest,C=HU
  • Issuer CN=Microsec e-Szigno Test Root CA 2008,OU=e-Szigno CA,O=Microsec Ltd.,L=Budapest,C=HU
  • Serial number 7.

The contained certificate, on the other hand, has

  • Subject CN=e-Szigno Test TSA2,OU=e-Szigno CA,O=Microsec Ltd.,L=Budapest,C=HU
  • Issuer CN=Microsec e-Szigno Test Root CA 2008,OU=e-Szigno CA,O=Microsec Ltd.,L=Budapest,C=HU
  • Serial number 5.

I assume that test TSA uses multiple signing devices / soft-tokens with individual certificates and the OP included the wrong one.

You, therefore, might want to include the correct certificate instead.

BTW, the time stamp in the PDF signed by iText contains a certificate matching the references in the stamp...

RFC 3161 time stamp requests can ask the TSA to include the signer certificate automatically. Bouncy Castle allows to set this flag like this:

TimeStampRequestGenerator reqgen = new TimeStampRequestGenerator();
reqgen.setCertReq(true); // <<<<<<<<<<<<<<<<<<<<<<<<<<
TimeStampRequest req = reqgen.generate(TSPAlgorithms.SHA1, data);

Instead of including the certificate yourself, you might try this.

LTV enabled

From the comments:

Just out of curiosity, what extra needs to be added to make a PDF LTV enabled?

To quote Leonard Rosenthol (PDF guru at Adobe):

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

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

(Jan 10, 2013; 7:07pm;Leonard Rosenthol on itext-general)