I created code that adds an image to an existing pdf document and then signs it, all using PDFBox (see code below).
The code nicely adds the image and the signature. However, in some documents, Acrobat Reader complains that "The signature byte range is invalid."
The problem seems to be the same as the problem described in this question. The answer to that question describes the problem in more detail: the problem is that my code leaves a mix of cross reference types in the document (streams and tables). Indeed, some documents won't even open because of the problems that this creates.
My question is: how do I prevent this? How do I add an image to an existing pdf document without creating multiple cross reference types?
public class TC3 implements SignatureInterface{
private char[] pin = "123456".toCharArray();
private BouncyCastleProvider provider = new BouncyCastleProvider();
private PrivateKey privKey;
private Certificate[] cert;
public TC3() throws Exception{
Security.addProvider(provider);
KeyStore keystore = KeyStore.getInstance("PKCS12", provider);
keystore.load(new FileInputStream(new File("resources/IIS_keystore.pfx")), pin.clone());
String alias = keystore.aliases().nextElement();
privKey = (PrivateKey) keystore.getKey(alias, pin);
cert = keystore.getCertificateChain(alias);
}
public void doSign() throws Exception{
byte inputBytes[] = IOUtils.toByteArray(new FileInputStream("resources/rooster.pdf"));
PDDocument pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));
PDJpeg ximage = new PDJpeg(pdDocument, ImageIO.read(new File("resources/logo.jpg")));
PDPage page = (PDPage)pdDocument.getDocumentCatalog().getAllPages().get(0);
PDPageContentStream contentStream = new PDPageContentStream(pdDocument, page, true, true);
contentStream.drawXObject(ximage, 50, 50, 356, 40);
contentStream.close();
ByteArrayOutputStream os = new ByteArrayOutputStream();
pdDocument.save(os);
os.flush();
pdDocument.close();
inputBytes = os.toByteArray();
pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));
PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signature.setName("signer name");
signature.setLocation("signer location");
signature.setReason("reason for signature");
signature.setSignDate(Calendar.getInstance());
pdDocument.addSignature(signature, this);
File outputDocument = new File("resources/signed.pdf");
ByteArrayInputStream fis = new ByteArrayInputStream(inputBytes);
FileOutputStream fos = new FileOutputStream(outputDocument);
byte[] buffer = new byte[8 * 1024];
int c;
while ((c = fis.read(buffer)) != -1)
{
fos.write(buffer, 0, c);
}
fis.close();
FileInputStream is = new FileInputStream(outputDocument);
pdDocument.saveIncremental(is, fos);
pdDocument.close();
}
public byte[] sign(InputStream content) {
CMSProcessableInputStream input = new CMSProcessableInputStream(content);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
List<Certificate> certList = Arrays.asList(cert);
CertStore certStore = null;
try{
certStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList), provider);
gen.addSigner(privKey, (X509Certificate) certList.get(0), CMSSignedGenerator.DIGEST_SHA256);
gen.addCertificatesAndCRLs(certStore);
CMSSignedData signedData = gen.generate(input, false, provider);
return signedData.getEncoded();
}catch (Exception e){}
return null;
}
public static void main(String[] args) throws Exception {
new TC3().doSign();
}