mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-11-01 01:21:18 +01:00 
			
		
		
		
	
							parent
							
								
									88f3594d80
								
							
						
					
					
						commit
						a7ed99084f
					
				@ -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,6 +92,8 @@ public class CertSignController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    class CreateSignature extends CreateSignatureBase {
 | 
			
		||||
        File imageFile;
 | 
			
		||||
 | 
			
		||||
        public CreateSignature(KeyStore keystore, char[] pin)
 | 
			
		||||
                throws KeyStoreException,
 | 
			
		||||
                UnrecoverableKeyException,
 | 
			
		||||
@ -69,6 +101,94 @@ public class CertSignController {
 | 
			
		||||
                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<PDField> 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<byte[]> 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());
 | 
			
		||||
 | 
			
		||||
            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,20 +291,17 @@ 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)
 | 
			
		||||
                PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
 | 
			
		||||
                pkInfo = ((PEMEncryptedKeyPair) pemObject)
 | 
			
		||||
                        .decryptKeyPair(decProv)
 | 
			
		||||
                        .getPrivateKeyInfo();
 | 
			
		||||
            } else {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								src/main/resources/static/images/signature.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/main/resources/static/images/signature.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 19 KiB  | 
@ -71,7 +71,7 @@
 | 
			
		||||
                    <label for="name" th:text="#{certSign.name}"></label> <input type="text" class="form-control" id="name" name="name">
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div class="mb-3">
 | 
			
		||||
                    <label for="pageNumber" th:text="#{pageNum}"></label> <input type="number" class="form-control" id="pageNumber" name="pageNumber" min="1" disabled>
 | 
			
		||||
                    <label for="pageNumber" th:text="#{pageNum}"></label> <input type="number" class="form-control" id="pageNumber" name="pageNumber" min="1">
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="mb-3 text-left">
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user