Skip to content

Commit

Permalink
chore: allows to sign pdf and add information on pdf
Browse files Browse the repository at this point in the history
  • Loading branch information
Fabien committed Nov 18, 2024
1 parent 515a4d8 commit cb4ba43
Show file tree
Hide file tree
Showing 16 changed files with 180 additions and 54 deletions.
1 change: 0 additions & 1 deletion dossierfacile-api-tenant/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.78</version>
</dependency>

<dependency>
Expand Down
1 change: 0 additions & 1 deletion dossierfacile-bo/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,6 @@
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.78</version>
</dependency>
<dependency>
<groupId>com.brevo</groupId>
Expand Down
8 changes: 8 additions & 0 deletions dossierfacile-pdf-generator/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
</dependency>
<!--Dependencies for Tests-->
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package fr.dossierfacile.api.pdfgenerator.service;

import fr.dossierfacile.api.pdfgenerator.service.interfaces.PdfSignatureService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.CollectionStore;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cglib.core.Local;
import org.springframework.stereotype.Service;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.Calendar;

@Slf4j
@Service
@RequiredArgsConstructor
public class PdfSignatureServiceImpl implements PdfSignatureService {

@Value("${pdf.signature.activation:false}")
private boolean signatureActivation;
@Value("${pdf.certificate:}")
private String certificate;

@Value("${pdf.private_key:}")
private String privateKey;

@Override
public void signAndSave(PDDocument document, ByteArrayOutputStream baos) throws Exception {
PDDocumentInformation information = new PDDocumentInformation();
information.setCreator("DossierFacile");
information.setCreationDate(Calendar.getInstance());
document.setDocumentInformation(information);

if (!signatureActivation || certificate == null || privateKey == null) {
document.save(baos);
} else {
PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signature.setName("DossierFacile");
signature.setReason("Document filigrané par DossierFacile");
signature.setSignDate(Calendar.getInstance());

document.addSignature(signature, content -> {
try {
return signInputStream(loadPrivateKey(), loadCertifcateChain(), content);
} catch (Exception e) {
throw new IOException("Unable to sign the document", e);
}
});

document.save(baos);
}
}


private Certificate[] loadCertifcateChain() throws Exception {
byte[] certBytes = Base64.getDecoder().decode(certificate);
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(certBytes));
return new Certificate[]{cert};
}

private PrivateKey loadPrivateKey() throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(privateKey);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
}

private byte[] signInputStream(PrivateKey privateKey, Certificate[] certificates, InputStream content) throws Exception {
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256withRSA").build(privateKey);
generator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build())
.build(contentSigner, (X509Certificate) certificates[0]));

CollectionStore certStore = new JcaCertStore(Arrays.asList(certificates));
generator.addCertificates(certStore);

byte[] contentBytes = content.readAllBytes();
CMSProcessableByteArray cmsData = new CMSProcessableByteArray(contentBytes);
CMSSignedData signedData = generator.generate(cmsData, false);

return signedData.getEncoded();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package fr.dossierfacile.api.pdfgenerator.service.interfaces;

import org.apache.pdfbox.pdmodel.PDDocument;

import java.io.ByteArrayOutputStream;

public interface PdfSignatureService {

void signAndSave(PDDocument document, ByteArrayOutputStream baos) throws Exception;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
import java.io.InputStream;

public interface PdfTemplate<T> {
InputStream render(T data) throws IOException;
InputStream render(T data) throws Exception;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import fr.dossierfacile.api.pdfgenerator.exception.TenantNotFoundException;
import fr.dossierfacile.api.pdfgenerator.model.TargetImageData;
import fr.dossierfacile.api.pdfgenerator.service.interfaces.DownloadService;
import fr.dossierfacile.api.pdfgenerator.service.interfaces.PdfSignatureService;
import fr.dossierfacile.api.pdfgenerator.service.interfaces.PdfTemplate;
import fr.dossierfacile.api.pdfgenerator.util.Fonts;
import fr.dossierfacile.api.pdfgenerator.util.PdfOptimizer;
Expand Down Expand Up @@ -39,7 +40,6 @@
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageFitWidthDestination;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
Expand All @@ -58,22 +58,15 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static fr.dossierfacile.api.pdfgenerator.service.templates.PdfFileTemplate.ATTACHMENTS_AND_CLARIFICATIONS;
import static fr.dossierfacile.api.pdfgenerator.service.templates.PdfFileTemplate.FIRST_TABLE_OF_CONTENT_PAGE;
import static fr.dossierfacile.api.pdfgenerator.service.templates.PdfFileTemplate.OTHER_TABLE_OF_CONTENT_PAGES;
import static fr.dossierfacile.api.pdfgenerator.service.templates.PdfFileTemplate.*;
import static org.apache.pdfbox.multipdf.PDFMergerUtility.DocumentMergeMode;
import static org.apache.pdfbox.pdmodel.font.Standard14Fonts.FontName;

Expand Down Expand Up @@ -241,6 +234,7 @@ public class ApartmentSharingPdfDocumentTemplate implements PdfTemplate<Apartmen
private final TenantCommonRepository tenantRepository;
private final DownloadService downloadDocumentService;
private final MessageSource messageSource;
private final PdfSignatureService pdfSignatureService;

private <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Map<Object, Boolean> seen = new ConcurrentHashMap<>();
Expand Down Expand Up @@ -518,11 +512,11 @@ private ByteArrayOutputStream mergePageInsideTemplate(PDDocument innerDocument,
layerUtility.wrapInSaveRestore(destPage);
layerUtility.appendFormAsLayer(destPage, innerPageAsForm, affineTransform, headerSentence);

document.addSignature(new PDSignature());
document.save(result);
pdfSignatureService.signAndSave(document, result);

}

} catch (IOException e) {
} catch (Exception e) {
log.error("Problem when printing attachment inside template of attachments");
log.error(e.getMessage(), e.getCause());
}
Expand Down Expand Up @@ -1375,8 +1369,7 @@ public InputStream render(ApartmentSharing apartmentSharing) throws IOException
PDDocument originDocument = Loader.loadPDF(result.toByteArray())) {

new PdfOptimizer().optimize(originDocument);
originDocument.addSignature(new PDSignature());
originDocument.save(finalResult);
pdfSignatureService.signAndSave(originDocument, finalResult);

return new ByteArrayInputStream(finalResult.toByteArray());
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import fr.dossierfacile.api.pdfgenerator.configuration.FeatureFlipping;
import fr.dossierfacile.api.pdfgenerator.model.FileInputStream;
import fr.dossierfacile.api.pdfgenerator.service.interfaces.PdfSignatureService;
import fr.dossierfacile.api.pdfgenerator.service.interfaces.PdfTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
Expand All @@ -19,8 +20,8 @@
@Qualifier("boIdentificationPdfDocumentTemplate")
public class BOIdentificationPdfDocumentTemplate extends BOPdfDocumentTemplate implements PdfTemplate<List<FileInputStream>> {

public BOIdentificationPdfDocumentTemplate(MessageSource messageSource, FeatureFlipping featureFlipping) {
super(messageSource, featureFlipping);
public BOIdentificationPdfDocumentTemplate(MessageSource messageSource, FeatureFlipping featureFlipping, PdfSignatureService pdfSignatureService) {
super(messageSource, featureFlipping, pdfSignatureService);
}

/**
Expand Down Expand Up @@ -91,7 +92,7 @@ protected BufferedImage smartCrop(BufferedImage image) {
while (col < gridSample.length && isBlankColumn(gridSample, col)) {
col++;
}
if( col == gridSample.length) {
if (col == gridSample.length) {
// unable to treat sounds empty
log.warn("Image sounds empty");
return image;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,22 @@
import com.drew.metadata.Metadata;
import com.drew.metadata.exif.ExifIFD0Directory;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.ChecksumException;
import com.google.zxing.DecodeHintType;
import com.google.zxing.FormatException;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.NotFoundException;
import com.google.zxing.Reader;
import com.google.zxing.Result;
import com.google.zxing.ResultPoint;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.multi.GenericMultipleBarcodeReader;
import com.google.zxing.multi.MultipleBarcodeReader;
import com.google.zxing.qrcode.QRCodeReader;
import com.twelvemonkeys.image.ImageUtil;
import fr.dossierfacile.api.pdfgenerator.configuration.FeatureFlipping;
import fr.dossierfacile.api.pdfgenerator.model.FileInputStream;
import fr.dossierfacile.api.pdfgenerator.model.PageDimension;
import fr.dossierfacile.api.pdfgenerator.model.PdfTemplateParameters;
import fr.dossierfacile.api.pdfgenerator.service.interfaces.PdfSignatureService;
import fr.dossierfacile.api.pdfgenerator.service.interfaces.PdfTemplate;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -36,7 +33,6 @@
import org.apache.pdfbox.pdmodel.PDPageTree;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.pdfbox.tools.imageio.ImageIOUtil;
Expand All @@ -54,7 +50,6 @@
import java.awt.image.Kernel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
Expand All @@ -77,6 +72,7 @@ public class BOPdfDocumentTemplate implements PdfTemplate<List<FileInputStream>>
private final MessageSource messageSource;

private final FeatureFlipping featureFlipping;
private final PdfSignatureService pdfSignatureService;

private final Color[] COLORS = {
new Color(64, 64, 64, 255),
Expand Down Expand Up @@ -111,12 +107,12 @@ private static ConvolveOp getGaussianBlurFilter(int radius, boolean horizontal)
}

@Override
public InputStream render(List<FileInputStream> data) throws IOException {
public InputStream render(List<FileInputStream> data) throws Exception {
return this.render(data,
messageSource.getMessage("tenant.pdf.watermark", null, DEFAULT_WATERMARK, locale));
}

public InputStream render(List<FileInputStream> data, String watermarkText) throws IOException {
public InputStream render(List<FileInputStream> data, String watermarkText) throws Exception {
final String watermarkToApply = StringUtils.isNotBlank(watermarkText) ? watermarkText + " " :
messageSource.getMessage("tenant.pdf.watermark.default", null, " https://filigrane.beta.gouv.fr/ ", locale);

Expand All @@ -133,11 +129,11 @@ public InputStream render(List<FileInputStream> data, String watermarkText) thro


try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
document.addSignature(new PDSignature());
document.save(baos);

pdfSignatureService.signAndSave(document, baos);
return new ByteArrayInputStream(baos.toByteArray());
}
} catch (IOException e) {
} catch (Exception e) {
log.error("Exception while generate BO PDF documents", e);
throw e;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import fr.dossierfacile.api.pdfgenerator.repository.GuarantorRepository;
import fr.dossierfacile.api.pdfgenerator.repository.TenantRepository;
import fr.dossierfacile.api.pdfgenerator.service.interfaces.PdfSignatureService;
import fr.dossierfacile.api.pdfgenerator.service.interfaces.PdfTemplate;
import fr.dossierfacile.api.pdfgenerator.util.Fonts;
import fr.dossierfacile.api.pdfgenerator.util.Utility;
Expand All @@ -18,7 +19,6 @@
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Service;
Expand All @@ -41,13 +41,14 @@ public class EmptyBOPdfDocumentTemplate implements PdfTemplate<Document> {
private final MessageSource messageSource;
private final TenantRepository tenantRepository;
private final GuarantorRepository guarantorRepository;
private final PdfSignatureService pdfSignatureService;

@Override
public InputStream render(Document document) throws IOException {
public InputStream render(Document document) throws Exception {
return new ByteArrayInputStream(createPdfFromTemplate(document).toByteArray());
}

private ByteArrayOutputStream createPdfFromTemplate(Document document) throws IOException {
private ByteArrayOutputStream createPdfFromTemplate(Document document) throws Exception {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PdfFileTemplate pdfTemplate = null;

Expand Down Expand Up @@ -94,10 +95,9 @@ private ByteArrayOutputStream createPdfFromTemplate(Document document) throws IO
Utility.addText(contentStream, width, startX, startY - 36, textElements.explanation, font, fontSize, alternativeFont);

contentStream.close();
pdDocument.addSignature(new PDSignature());
pdDocument.save(outputStream);
pdfSignatureService.signAndSave(pdDocument, outputStream);

} catch (IOException e) {
} catch (Exception e) {
log.error("Error on pdf creation", e);
throw e;
}
Expand Down
Loading

0 comments on commit cb4ba43

Please sign in to comment.