package mkl.testarea.pdfbox2.sign; import java.awt.Color; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.PrivateKey; import java.security.Security; import java.security.cert.Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.List; import org.apache.pdfbox.Loader; import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.io.IOUtils; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDResources; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDStream; import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.pdmodel.font.PDType1Font; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream; import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDField; import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.cms.CMSObjectIdentifiers; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaCertStore; import org.bouncycastle.cms.CMSException; import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.cms.CMSSignedDataGenerator; import org.bouncycastle.cms.CMSTypedData; import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import org.bouncycastle.util.Store; import org.junit.BeforeClass; import org.junit.Test; public class CreateMultipleVisualizations implements SignatureInterface { final static File RESULT_FOLDER = new File("target/test-outputs", "sign"); public static final String KEYSTORE = "keystores/demo-rsa2048.ks"; public static final char[] PASSWORD = "demo-rsa2048".toCharArray(); public static KeyStore ks = null; public static PrivateKey pk = null; public static Certificate[] chain = null; @BeforeClass public static void setUpBeforeClass() throws Exception { RESULT_FOLDER.mkdirs(); BouncyCastleProvider bcp = new BouncyCastleProvider(); Security.insertProviderAt(bcp, 1); ks = KeyStore.getInstance(KeyStore.getDefaultType()); ks.load(new FileInputStream(KEYSTORE), PASSWORD); String alias = (String) ks.aliases().nextElement(); pk = (PrivateKey) ks.getKey(alias, PASSWORD); chain = ks.getCertificateChain(alias); } /** * <a href="https://stackoverflow.com/questions/52829507/multiple-esign-using-pdfbox-2-0-12-java"> * Multiple esign using pdfbox 2.0.12 java? * </a> * <p> * This test demonstrates how to create a single signature in multiple signature * fields with one widget annotation each only referenced from a single page each * only. (Actually there is an extra invisible signature; it is possible to get * rid of it with some more code.) * </p> */ @Test public void testCreateSignatureWithMultipleVisualizations() throws IOException { try ( InputStream resource = getClass().getResourceAsStream("/mkl/testarea/pdfbox2/analyze/test-rivu.pdf"); OutputStream result = new FileOutputStream(new File(RESULT_FOLDER, "testSignedMultipleVisualizations.pdf")); PDDocument pdDocument = Loader.loadPDF(resource) ) { PDAcroForm acroForm = pdDocument.getDocumentCatalog().getAcroForm(); if (acroForm == null) { pdDocument.getDocumentCatalog().setAcroForm(acroForm = new PDAcroForm(pdDocument)); } acroForm.setSignaturesExist(true); acroForm.setAppendOnly(true); acroForm.getCOSObject().setDirect(true); PDRectangle rectangle = new PDRectangle(100, 600, 300, 100); PDSignature signature = new PDSignature(); signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED); signature.setName("Example User"); signature.setLocation("Los Angeles, CA"); signature.setReason("Testing"); signature.setSignDate(Calendar.getInstance()); pdDocument.addSignature(signature, this); for (PDPage pdPage : pdDocument.getPages()) { addSignatureField(pdDocument, pdPage, rectangle, signature); } pdDocument.saveIncremental(result); } } /** * <a href="https://stackoverflow.com/questions/52829507/multiple-esign-using-pdfbox-2-0-12-java"> * Multiple esign using pdfbox 2.0.12 java? * </a> * <br/> * <a href="https://drive.google.com/open?id=1DKSApmjrT424wT92yBdynKUkvR00x9i2"> * C10000000713071804294534.pdf * </a> * <p> * This test demonstrates how to create a single signature in multiple signature * fields with one widget annotation each only referenced from a single page each * only. (Actually there is an extra invisible signature; it is possible to get * rid of it with some more code.) It uses the test file provided by the OP. * </p> */ @Test public void testCreateSignatureWithMultipleVisualizationsC10000000713071804294534() throws IOException { try ( InputStream resource = getClass().getResourceAsStream("C10000000713071804294534.pdf"); OutputStream result = new FileOutputStream(new File(RESULT_FOLDER, "C10000000713071804294534-SignedMultipleVisualizations.pdf")); PDDocument pdDocument = Loader.loadPDF(resource) ) { PDAcroForm acroForm = pdDocument.getDocumentCatalog().getAcroForm(); if (acroForm == null) { pdDocument.getDocumentCatalog().setAcroForm(acroForm = new PDAcroForm(pdDocument)); } acroForm.setSignaturesExist(true); acroForm.setAppendOnly(true); acroForm.getCOSObject().setDirect(true); PDRectangle rectangle = new PDRectangle(100, 600, 300, 100); PDSignature signature = new PDSignature(); signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED); signature.setName("Example User"); signature.setLocation("Los Angeles, CA"); signature.setReason("Testing"); signature.setSignDate(Calendar.getInstance()); pdDocument.addSignature(signature, this); for (PDPage pdPage : pdDocument.getPages()) { addSignatureField(pdDocument, pdPage, rectangle, signature); } pdDocument.saveIncremental(result); } } /** * Based on <code>org.apache.pdfbox.examples.signature.CreateVisibleSignature2.createVisualSignatureTemplate(PDDocument, int, PDRectangle)</code> * from the pdfbox examples artifact but severely simplified and now used as a method to create the actual signature fields, not merely a template. */ void addSignatureField(PDDocument pdDocument, PDPage pdPage, PDRectangle rectangle, PDSignature signature) throws IOException { PDAcroForm acroForm = pdDocument.getDocumentCatalog().getAcroForm(); List<PDField> acroFormFields = acroForm.getFields(); PDSignatureField signatureField = new PDSignatureField(acroForm); signatureField.setValue(signature); PDAnnotationWidget widget = signatureField.getWidgets().get(0); acroFormFields.add(signatureField); widget.setRectangle(rectangle); widget.setPage(pdPage); // from PDVisualSigBuilder.createHolderForm() PDStream stream = new PDStream(pdDocument); PDFormXObject form = new PDFormXObject(stream); PDResources res = new PDResources(); form.setResources(res); form.setFormType(1); PDRectangle bbox = new PDRectangle(rectangle.getWidth(), rectangle.getHeight()); float height = bbox.getHeight(); form.setBBox(bbox); PDFont font = PDType1Font.HELVETICA_BOLD; // from PDVisualSigBuilder.createAppearanceDictionary() PDAppearanceDictionary appearance = new PDAppearanceDictionary(); appearance.getCOSObject().setDirect(true); PDAppearanceStream appearanceStream = new PDAppearanceStream(form.getCOSObject()); appearance.setNormalAppearance(appearanceStream); widget.setAppearance(appearance); try (PDPageContentStream cs = new PDPageContentStream(pdDocument, appearanceStream)) { // show background (just for debugging, to see the rect size + position) cs.setNonStrokingColor(Color.yellow); cs.addRect(-5000, -5000, 10000, 10000); cs.fill(); float fontSize = 10; float leading = fontSize * 1.5f; cs.beginText(); cs.setFont(font, fontSize); cs.setNonStrokingColor(Color.black); cs.newLineAtOffset(fontSize, height - leading); cs.setLeading(leading); cs.showText("Signature text"); cs.newLine(); cs.showText("some additional Information"); cs.newLine(); cs.showText("let's keep talking"); cs.endText(); } pdPage.getAnnotations().add(widget); COSDictionary pageTreeObject = pdPage.getCOSObject(); while (pageTreeObject != null) { pageTreeObject.setNeedToBeUpdated(true); pageTreeObject = (COSDictionary) pageTreeObject.getDictionaryObject(COSName.PARENT); } } /** * Copy of <code>org.apache.pdfbox.examples.signature.CreateSignatureBase.sign(InputStream)</code> * from the pdfbox examples artifact. */ @Override public byte[] sign(InputStream content) throws IOException { try { List<Certificate> certList = new ArrayList<>(); certList.addAll(Arrays.asList(chain)); Store<?> certs = new JcaCertStore(certList); CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); org.bouncycastle.asn1.x509.Certificate cert = org.bouncycastle.asn1.x509.Certificate.getInstance(chain[0].getEncoded()); ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA256WithRSA").build(pk); gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(sha1Signer, new X509CertificateHolder(cert))); gen.addCertificates(certs); CMSProcessableInputStream msg = new CMSProcessableInputStream(content); CMSSignedData signedData = gen.generate(msg, false); return signedData.getEncoded(); } catch (GeneralSecurityException | CMSException | OperatorCreationException e) { throw new IOException(e); } } /** * Copy of <code>org.apache.pdfbox.examples.signature.CMSProcessableInputStream</code> * from the pdfbox examples artifact. */ static class CMSProcessableInputStream implements CMSTypedData { private InputStream in; private final ASN1ObjectIdentifier contentType; CMSProcessableInputStream(InputStream is) { this(new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId()), is); } CMSProcessableInputStream(ASN1ObjectIdentifier type, InputStream is) { contentType = type; in = is; } @Override public Object getContent() { return in; } @Override public void write(OutputStream out) throws IOException, CMSException { // read the content only one time IOUtils.copy(in, out); in.close(); } @Override public ASN1ObjectIdentifier getContentType() { return contentType; } } }