Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: allows to sign pdf and add information on pdf #907

Merged
merged 1 commit into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading