Should anybody care, David at BC found the bug that managed to work for
prior BC versions, but no longer does under 1.51. We changed to this
method in CMSTypedDataInputStream to return 'in' instead of 'null' and
it works under 1.50 as well as 1.51:
@Override
public Object getContent()
{
return in;
}
On 10/24/2014 3:06 PM, David Wall wrote:
I'm now getting an invalid digital signature that I created on PDFs we
generate (via wkhtmltopdf and PDFBox 1.8.7). It says "At least one
signature is invalid" but I previously could create them with valid
signatures. This occurred when going from BouncyCastle 1.50 to 1.51,
and if I go back to 1.50, it works fine.
The invalid signature complains that the "Document has been altered or
corrupted since it was signed".
Here's a link to an existing PDF that has an invalid signature:
http://open.esignforms.com/pdfboxlist/MyDocumentsGOOD.pdf (using BC 1.50)
http://open.esignforms.com/pdfboxlist/MyDocumentsBAD.pdf (using BC 1.51)
I am using Java 7.
Here are the relevant Java code:
boolean signPdf(File pdfFile, File signedPdfFile)
{
FileInputStream fis = null;
FileOutputStream fos = null;
PDDocument doc = null;
try
{
fis = new FileInputStream(pdfFile);
fos = new FileOutputStream(signedPdfFile);
int readCount;
byte[] buffer = new byte[8 * 1024];
while ((readCount = fis.read(buffer)) != -1)
{
fos.write(buffer, 0, readCount);
}
fis.close();
fis = new FileInputStream(signedPdfFile);
doc = PDDocument.load(pdfFile);
PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signature.setName("Open eSignForms Export PDF Integrity
Lock");
signature.setLocation(Application.getInstance().getExternalContextPath());
signature.setReason("Used to ensure that an exported PDF
has not been tampered with since its generation by Open eSignForms
deployment id: " +
Application.getInstance().getDeployId());
signature.setSignDate(Calendar.getInstance());
doc.addSignature(signature, this);
doc.saveIncremental(fis, fos);
return true;
}
catch( Exception e )
{
_logger.error("signPdf() - Failed to sign the PDF",e);
return false;
}
finally
{
if ( fis != null ) try { fis.close(); } catch( Exception e
) {}
if ( fos != null ) try { fos.close(); } catch( Exception e
) {}
if ( doc != null ) try { doc.close(); } catch( Exception e
) {}
}
}
@Override
public byte[] sign(InputStream is) throws SignatureException,
IOException
{
Application app = Application.getInstance();
try
{
String provider = app.getPublicKeyGenerator().getProvider();
SignatureKey signatureKey = app.getSignatureKey();
X509Certificate cert = signatureKey.getX509Certificate();
Store certStore = new JcaCertStore(Arrays.asList(cert));
CMSTypedDataInputStream input = new
CMSTypedDataInputStream(is);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
ContentSigner sha512Signer = new
JcaContentSignerBuilder(PublicKeyGenerator.SIGNATURE_ALGORITHM).setProvider(provider).build(signatureKey.getPrivateKey());
gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(
new
JcaDigestCalculatorProviderBuilder().setProvider(provider).build()).build(sha512Signer,
cert));
gen.addCertificates(certStore);
CMSSignedData signedData = gen.generate(input, false);
return signedData.getEncoded();
}
catch (Exception e)
{
_logger.error("sign() - Problem while preparing PDF
signature",e);
return null;
}
}
class CMSTypedDataInputStream implements CMSTypedData
{
InputStream in;
public CMSTypedDataInputStream(InputStream is)
{
in = is;
}
@Override
public ASN1ObjectIdentifier getContentType()
{
return PKCSObjectIdentifiers.data;
}
@Override
public Object getContent()
{
return null;
}
@Override
public void write(OutputStream out) throws IOException,
CMSException
{
byte[] buffer = new byte[8 * 1024];
int read;
while( (read = in.read(buffer)) != -1 )
{
out.write(buffer, 0, read);
}
in.close();
}
}