David Wall
2014-10-24 01:16:00 UTC
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 again, so there's something
about 1.51 that breaks my current code.
The invalid signature complains that the "Document has been altered or
corrupted since it was signed".
Here's a link to an existing PDFs that show the issue:
http://open.esignforms.com/pdfboxlist/MyDocumentsGOOD.pdf (using BC
1.50 the signature is valid)
http://open.esignforms.com/pdfboxlist/MyDocumentsBAD.pdf (using BC 1.51
the signature is invalid)
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();
}
}
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 again, so there's something
about 1.51 that breaks my current code.
The invalid signature complains that the "Document has been altered or
corrupted since it was signed".
Here's a link to an existing PDFs that show the issue:
http://open.esignforms.com/pdfboxlist/MyDocumentsGOOD.pdf (using BC
1.50 the signature is valid)
http://open.esignforms.com/pdfboxlist/MyDocumentsBAD.pdf (using BC 1.51
the signature is invalid)
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();
}
}