From a7ed99084fb738c9af7fa8c43ae98b4cce7b135f Mon Sep 17 00:00:00 2001 From: Eric <71648843+sbplat@users.noreply.github.com> Date: Thu, 24 Oct 2024 02:08:09 -0400 Subject: [PATCH] visual certificate signing (#2084) add visual digital signature --- .../api/security/CertSignController.java | 173 +++++++++++++++--- .../resources/static/images/signature.png | Bin 0 -> 19683 bytes .../templates/security/cert-sign.html | 2 +- 3 files changed, 150 insertions(+), 25 deletions(-) create mode 100644 src/main/resources/static/images/signature.png diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java b/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java index c37fcc9c..b36f8c90 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java @@ -1,8 +1,11 @@ package stirling.software.SPDF.controller.api.security; +import java.awt.Color; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.security.KeyStore; @@ -14,12 +17,38 @@ import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.Calendar; +import java.util.List; import org.apache.pdfbox.examples.signature.CreateSignatureBase; 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.font.Standard14Fonts.FontName; +import org.apache.pdfbox.pdmodel.graphics.blend.BlendMode; +import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; +import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState; +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.SignatureOptions; +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.apache.pdfbox.util.Matrix; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.asn1.x500.RDN; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.asn1.x500.style.IETFUtils; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMDecryptorProvider; import org.bouncycastle.openssl.PEMEncryptedKeyPair; @@ -35,6 +64,7 @@ import org.bouncycastle.pkcs.PKCSException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ClassPathResource; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; @@ -62,13 +92,103 @@ public class CertSignController { } class CreateSignature extends CreateSignatureBase { + File imageFile; + public CreateSignature(KeyStore keystore, char[] pin) throws KeyStoreException, - UnrecoverableKeyException, - NoSuchAlgorithmException, - IOException, - CertificateException { + UnrecoverableKeyException, + NoSuchAlgorithmException, + IOException, + CertificateException { super(keystore, pin); + ClassPathResource resource = new ClassPathResource("static/images/signature.png"); + imageFile = resource.getFile(); + } + + public InputStream createVisibleSignature(PDDocument srcDoc, PDSignature signature, Integer pageNumber, + Boolean showImage) throws IOException { + // modified from org.apache.pdfbox.examples.signature.CreateVisibleSignature2 + try (PDDocument doc = new PDDocument()) { + PDPage page = new PDPage(srcDoc.getPage(pageNumber).getMediaBox()); + doc.addPage(page); + PDAcroForm acroForm = new PDAcroForm(doc); + doc.getDocumentCatalog().setAcroForm(acroForm); + PDSignatureField signatureField = new PDSignatureField(acroForm); + PDAnnotationWidget widget = signatureField.getWidgets().get(0); + List acroFormFields = acroForm.getFields(); + acroForm.setSignaturesExist(true); + acroForm.setAppendOnly(true); + acroForm.getCOSObject().setDirect(true); + acroFormFields.add(signatureField); + + PDRectangle rect = new PDRectangle(0, 0, 200, 50); + + widget.setRectangle(rect); + + // from PDVisualSigBuilder.createHolderForm() + PDStream stream = new PDStream(doc); + PDFormXObject form = new PDFormXObject(stream); + PDResources res = new PDResources(); + form.setResources(res); + form.setFormType(1); + PDRectangle bbox = new PDRectangle(rect.getWidth(), rect.getHeight()); + float height = bbox.getHeight(); + form.setBBox(bbox); + PDFont font = new PDType1Font(FontName.TIMES_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(doc, appearanceStream)) { + if (showImage) { + cs.saveGraphicsState(); + PDExtendedGraphicsState extState = new PDExtendedGraphicsState(); + extState.setBlendMode(BlendMode.MULTIPLY); + extState.setNonStrokingAlphaConstant(0.5f); + cs.setGraphicsStateParameters(extState); + cs.transform(Matrix.getScaleInstance(0.08f, 0.08f)); + PDImageXObject img = PDImageXObject.createFromFileByExtension(imageFile, + doc); + cs.drawImage(img, 100, 0); + cs.restoreGraphicsState(); + } + + // show text + 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); + + X509Certificate cert = (X509Certificate) getCertificateChain()[0]; + + // https://stackoverflow.com/questions/2914521/ + X500Name x500Name = new X500Name(cert.getSubjectX500Principal().getName()); + RDN cn = x500Name.getRDNs(BCStyle.CN)[0]; + String name = IETFUtils.valueToString(cn.getFirst().getValue()); + + String date = signature.getSignDate().getTime().toString(); + String reason = signature.getReason(); + + cs.showText("Signed by " + name); + cs.newLine(); + cs.showText(date); + cs.newLine(); + cs.showText(reason); + + cs.endText(); + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + doc.save(baos); + return new ByteArrayInputStream(baos.toByteArray()); + } } } @@ -80,10 +200,7 @@ public class CertSignController { } @PostMapping(consumes = "multipart/form-data", value = "/cert-sign") - @Operation( - summary = "Sign PDF with a Digital Certificate", - description = - "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:SISO") + @Operation(summary = "Sign PDF with a Digital Certificate", description = "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:SISO") public ResponseEntity signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request) throws Exception { MultipartFile pdf = request.getFileInput(); @@ -97,7 +214,7 @@ public class CertSignController { String reason = request.getReason(); String location = request.getLocation(); String name = request.getName(); - Integer pageNumber = request.getPageNumber(); + Integer pageNumber = request.getPageNumber() - 1; if (certType == null) { throw new IllegalArgumentException("Cert type must be provided"); @@ -112,7 +229,7 @@ public class CertSignController { PrivateKey privateKey = getPrivateKeyFromPEM(privateKeyFile.getBytes(), password); Certificate cert = (Certificate) getCertificateFromPEM(certFile.getBytes()); ks.setKeyEntry( - "alias", privateKey, password.toCharArray(), new Certificate[] {cert}); + "alias", privateKey, password.toCharArray(), new Certificate[] { cert }); break; case "PKCS12": ks = KeyStore.getInstance("PKCS12"); @@ -126,11 +243,10 @@ public class CertSignController { throw new IllegalArgumentException("Invalid cert type: " + certType); } - // TODO: page number - CreateSignature createSignature = new CreateSignature(ks, password.toCharArray()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); - sign(pdfDocumentFactory, pdf.getBytes(), baos, createSignature, name, location, reason); + sign(pdfDocumentFactory, pdf.getBytes(), baos, createSignature, showSignature, pageNumber, name, location, + reason); return WebResponseUtils.boasToWebResponse( baos, Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "") @@ -142,6 +258,8 @@ public class CertSignController { byte[] input, OutputStream output, CreateSignature instance, + Boolean showSignature, + Integer pageNumber, String name, String location, String reason) { @@ -154,7 +272,17 @@ public class CertSignController { signature.setReason(reason); signature.setSignDate(Calendar.getInstance()); - doc.addSignature(signature, instance); + if (showSignature) { + SignatureOptions signatureOptions = new SignatureOptions(); + signatureOptions + .setVisualSignature(instance.createVisibleSignature(doc, signature, pageNumber, true)); + signatureOptions.setPage(pageNumber); + + doc.addSignature(signature, instance, signatureOptions); + + } else { + doc.addSignature(signature, instance); + } doc.saveIncremental(output); } catch (Exception e) { logger.error("exception", e); @@ -163,22 +291,19 @@ public class CertSignController { private PrivateKey getPrivateKeyFromPEM(byte[] pemBytes, String password) throws IOException, OperatorCreationException, PKCSException { - try (PEMParser pemParser = - new PEMParser(new InputStreamReader(new ByteArrayInputStream(pemBytes)))) { + try (PEMParser pemParser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(pemBytes)))) { Object pemObject = pemParser.readObject(); JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); PrivateKeyInfo pkInfo; if (pemObject instanceof PKCS8EncryptedPrivateKeyInfo) { - InputDecryptorProvider decProv = - new JceOpenSSLPKCS8DecryptorProviderBuilder().build(password.toCharArray()); + InputDecryptorProvider decProv = new JceOpenSSLPKCS8DecryptorProviderBuilder() + .build(password.toCharArray()); pkInfo = ((PKCS8EncryptedPrivateKeyInfo) pemObject).decryptPrivateKeyInfo(decProv); } else if (pemObject instanceof PEMEncryptedKeyPair) { - PEMDecryptorProvider decProv = - new JcePEMDecryptorProviderBuilder().build(password.toCharArray()); - pkInfo = - ((PEMEncryptedKeyPair) pemObject) - .decryptKeyPair(decProv) - .getPrivateKeyInfo(); + PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password.toCharArray()); + pkInfo = ((PEMEncryptedKeyPair) pemObject) + .decryptKeyPair(decProv) + .getPrivateKeyInfo(); } else { pkInfo = ((PEMKeyPair) pemObject).getPrivateKeyInfo(); } diff --git a/src/main/resources/static/images/signature.png b/src/main/resources/static/images/signature.png new file mode 100644 index 0000000000000000000000000000000000000000..1adfcedc3c2fe809dcbecedf3253d103094e9d65 GIT binary patch literal 19683 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 Xmg%Tio`5CDL7$7`Ifr^|K>U9JudKk& literal 0 HcmV?d00001 diff --git a/src/main/resources/templates/security/cert-sign.html b/src/main/resources/templates/security/cert-sign.html index 173cd06d..4c3a34d0 100644 --- a/src/main/resources/templates/security/cert-sign.html +++ b/src/main/resources/templates/security/cert-sign.html @@ -71,7 +71,7 @@
- +