From 2140c0ed26eb17ee05d28d3a3e462c180f957bf2 Mon Sep 17 00:00:00 2001 From: grausof Date: Tue, 6 Sep 2022 12:20:12 +0200 Subject: [PATCH 01/25] Add function for PAdES PDF make --- demo.pdf | Bin 0 -> 7016 bytes src/main/java/it/pagopa/dss/App.java | 10 - src/main/java/it/pagopa/dss/PdfBoxArray.java | 162 +++++++++++++++ src/main/java/it/pagopa/dss/PdfBoxDict.java | 191 ++++++++++++++++++ .../java/it/pagopa/dss/SignatureService.java | 14 ++ .../it/pagopa/dss/SignatureServiceImpl.java | 126 ++++++++++++ .../pagopa/dss/SignatureServiceInterface.java | 63 ++++++ src/main/java/it/pagopa/dss/Utility.java | 82 ++++++++ .../exception/SignatureServiceException.java | 12 ++ .../it/pagopa/dss/SignatureServiceTest.java | 36 ++++ 10 files changed, 686 insertions(+), 10 deletions(-) create mode 100644 demo.pdf delete mode 100644 src/main/java/it/pagopa/dss/App.java create mode 100644 src/main/java/it/pagopa/dss/PdfBoxArray.java create mode 100644 src/main/java/it/pagopa/dss/PdfBoxDict.java create mode 100644 src/main/java/it/pagopa/dss/SignatureService.java create mode 100644 src/main/java/it/pagopa/dss/SignatureServiceImpl.java create mode 100644 src/main/java/it/pagopa/dss/SignatureServiceInterface.java create mode 100644 src/main/java/it/pagopa/dss/Utility.java create mode 100644 src/main/java/it/pagopa/dss/exception/SignatureServiceException.java create mode 100644 src/test/java/it/pagopa/dss/SignatureServiceTest.java diff --git a/demo.pdf b/demo.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4a79cee70607851b86ee4be5cfe8e6289ea8d813 GIT binary patch literal 7016 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 DpRfer literal 0 HcmV?d00001 diff --git a/src/main/java/it/pagopa/dss/App.java b/src/main/java/it/pagopa/dss/App.java deleted file mode 100644 index 05e210e..0000000 --- a/src/main/java/it/pagopa/dss/App.java +++ /dev/null @@ -1,10 +0,0 @@ -package it.pagopa.dss; - -final class App { - - private App() {} - - public static void main(String[] args) { - System.out.println("Welcome!"); - } -} diff --git a/src/main/java/it/pagopa/dss/PdfBoxArray.java b/src/main/java/it/pagopa/dss/PdfBoxArray.java new file mode 100644 index 0000000..e2d70b7 --- /dev/null +++ b/src/main/java/it/pagopa/dss/PdfBoxArray.java @@ -0,0 +1,162 @@ +package it.pagopa.dss; + +import eu.europa.esig.dss.model.DSSException; +import eu.europa.esig.dss.pdf.PdfArray; +import eu.europa.esig.dss.pdf.PdfDict; +import eu.europa.esig.dss.spi.DSSUtils; +import java.io.IOException; +import java.io.InputStream; +import org.apache.pdfbox.cos.COSArray; +import org.apache.pdfbox.cos.COSBase; +import org.apache.pdfbox.cos.COSBoolean; +import org.apache.pdfbox.cos.COSDictionary; +import org.apache.pdfbox.cos.COSFloat; +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.cos.COSNull; +import org.apache.pdfbox.cos.COSNumber; +import org.apache.pdfbox.cos.COSObject; +import org.apache.pdfbox.cos.COSStream; +import org.apache.pdfbox.cos.COSString; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The PDFBox implementation of {@code eu.europa.esig.dss.pdf.PdfArray}. + */ +class PdfBoxArray implements PdfArray { + + private static final Logger LOG = LoggerFactory.getLogger(PdfBoxArray.class); + + /** The PDFBox object */ + private COSArray wrapped; + + /** + * The document + * + * NOTE for developers: Retain this reference ! PDDocument must not be garbage collected + */ + private PDDocument document; + + /** + * Default constructor + * + * @param wrapped {@link COSArray} + * @param document {@link PDDocument} + */ + PdfBoxArray(COSArray wrapped, PDDocument document) { + this.wrapped = wrapped; + this.document = document; + } + + @Override + public int size() { + return wrapped.size(); + } + + @Override + public byte[] getStreamBytes(int i) throws IOException { + COSBase val = wrapped.get(i); + return toBytes(val); + } + + private byte[] toBytes(COSBase val) throws IOException { + COSStream cosStream = null; + if (val instanceof COSObject) { + COSObject o = (COSObject) val; + final COSBase object = o.getObject(); + if (object instanceof COSStream) { + cosStream = (COSStream) object; + } + } + if (cosStream == null) { + throw new DSSException( + "Cannot find value for " + val + " of class " + val.getClass() + ); + } + try (InputStream is = cosStream.createInputStream()) { + return DSSUtils.toByteArray(is); + } + } + + @Override + public Long getObjectNumber(int i) { + COSBase val = wrapped.get(i); + if (val instanceof COSObject) { + return ((COSObject) val).getObjectNumber(); + } + return null; + } + + @Override + public Number getNumber(int i) { + COSBase val = wrapped.get(i); + if (val != null) { + if (val instanceof COSFloat) { + return ((COSFloat) val).floatValue(); + } else if (val instanceof COSNumber) { + return ((COSNumber) val).longValue(); + } + } + return null; + } + + @Override + public String getString(int i) { + return wrapped.getString(i); + } + + @Override + public PdfDict getAsDict(int i) { + COSDictionary cosDictionary = null; + COSBase cosBaseObject = wrapped.get(i); + if (cosBaseObject instanceof COSDictionary) { + cosDictionary = (COSDictionary) cosBaseObject; + } else if (cosBaseObject instanceof COSObject) { + COSObject cosObject = (COSObject) cosBaseObject; + cosDictionary = (COSDictionary) cosObject.getObject(); + } + if (cosDictionary != null) { + return new PdfBoxDict(cosDictionary, document); + } + LOG.warn("Unable to extract array entry as dictionary!"); + return null; + } + + @Override + public Object getObject(int i) { + COSBase dictionaryObject = wrapped.getObject(i); + if (dictionaryObject == null) { + return null; + } + if ( + dictionaryObject instanceof COSDictionary || dictionaryObject instanceof COSObject + ) { + return getAsDict(i); + } else if (dictionaryObject instanceof COSArray) { + return new PdfBoxArray((COSArray) dictionaryObject, document); + } else if (dictionaryObject instanceof COSString) { + return getString(i); + } else if (dictionaryObject instanceof COSName) { + return wrapped.getName(i); + } else if (dictionaryObject instanceof COSNumber) { + return getNumber(i); + } else if (dictionaryObject instanceof COSBoolean) { + return ((COSBoolean) dictionaryObject).getValueAsObject(); + } else if (dictionaryObject instanceof COSNull) { + return null; + } else { + LOG.warn( + "Unable to process an entry on position '{}' of type '{}'.", + i, + dictionaryObject.getClass() + ); + } + return null; + } + + @Override + public String toString() { + return wrapped.toString(); + } +} diff --git a/src/main/java/it/pagopa/dss/PdfBoxDict.java b/src/main/java/it/pagopa/dss/PdfBoxDict.java new file mode 100644 index 0000000..855f6f3 --- /dev/null +++ b/src/main/java/it/pagopa/dss/PdfBoxDict.java @@ -0,0 +1,191 @@ +package it.pagopa.dss; + +import eu.europa.esig.dss.pdf.PdfArray; +import eu.europa.esig.dss.pdf.PdfDict; +import eu.europa.esig.dss.utils.Utils; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import org.apache.pdfbox.cos.COSArray; +import org.apache.pdfbox.cos.COSBase; +import org.apache.pdfbox.cos.COSBoolean; +import org.apache.pdfbox.cos.COSDictionary; +import org.apache.pdfbox.cos.COSFloat; +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.cos.COSNull; +import org.apache.pdfbox.cos.COSNumber; +import org.apache.pdfbox.cos.COSObject; +import org.apache.pdfbox.cos.COSStream; +import org.apache.pdfbox.cos.COSString; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The PDFBox implementation of {@code eu.europa.esig.dss.pdf.PdfDict} + */ +class PdfBoxDict implements PdfDict { + + private static final Logger LOG = LoggerFactory.getLogger(PdfBoxDict.class); + + /** The PDFBox object */ + private COSDictionary wrapped; + + /** The document */ + private PDDocument document; + + /** + * Default constructor + * + * @param wrapped {@link COSDictionary} + * @param document {@link PDDocument} + */ + PdfBoxDict(COSDictionary wrapped, PDDocument document) { + Objects.requireNonNull(wrapped, "Pdf dictionary shall be provided!"); + Objects.requireNonNull(document, "Pdf document shall be provided!"); + this.wrapped = wrapped; + this.document = document; + } + + @Override + public PdfDict getAsDict(String name) { + COSBase cosBaseObject = wrapped.getDictionaryObject(name); + if (cosBaseObject == null) { + return null; + } + COSDictionary cosDictionary; + if (cosBaseObject instanceof COSDictionary) { + cosDictionary = (COSDictionary) cosBaseObject; + } else if (cosBaseObject instanceof COSObject) { + COSObject cosObject = (COSObject) cosBaseObject; + cosDictionary = (COSDictionary) cosObject.getObject(); + } else { + LOG.warn("Unable to extract entry with name '{}' as dictionary!", name); + return null; + } + return new PdfBoxDict(cosDictionary, document); + } + + @Override + public PdfArray getAsArray(String name) { + COSArray array = (COSArray) wrapped.getDictionaryObject(name); + if (array == null) { + return null; + } + return new PdfBoxArray(array, document); + } + + @Override + public byte[] getBinariesValue(String name) throws IOException { + COSBase val = wrapped.getDictionaryObject(name); + if (val instanceof COSString) { + return ((COSString) val).getBytes(); + } + throw new IOException( + name + " was expected to be a COSString element but was : " + val + ); + } + + @Override + public String[] list() { + final Set cosNames = wrapped.keySet(); + List result = new ArrayList<>(cosNames.size()); + for (final COSName cosName : cosNames) { + final String name = cosName.getName(); + result.add(name); + } + return result.toArray(new String[result.size()]); + } + + @Override + public String getStringValue(String name) { + return wrapped.getString(name); + } + + @Override + public String getNameValue(String name) { + return wrapped.getNameAsString(name); + } + + @Override + public Date getDateValue(String name) { + Calendar cal = wrapped.getDate(name); + if (cal != null) { + return cal.getTime(); + } + return null; + } + + @Override + public Number getNumberValue(String name) { + COSBase val = wrapped.getDictionaryObject(name); + if (val != null) { + if (val instanceof COSFloat) { + return ((COSFloat) val).floatValue(); + } else if (val instanceof COSNumber) { + return ((COSNumber) val).longValue(); + } + } + return null; + } + + @Override + public Object getObject(String name) { + COSBase dictionaryObject = wrapped.getDictionaryObject(name); + if (dictionaryObject == null) { + return null; + } else if ( + dictionaryObject instanceof COSDictionary || dictionaryObject instanceof COSObject + ) { + return getAsDict(name); + } else if (dictionaryObject instanceof COSArray) { + return getAsArray(name); + } else if (dictionaryObject instanceof COSString) { + return getStringValue(name); + } else if (dictionaryObject instanceof COSName) { + return getNameValue(name); + } else if (dictionaryObject instanceof COSNumber) { + return getNumberValue(name); + } else if (dictionaryObject instanceof COSBoolean) { + return ((COSBoolean) dictionaryObject).getValueAsObject(); + } else if (dictionaryObject instanceof COSNull) { + return null; + } else { + LOG.warn( + "Unable to process an entry with name '{}' of type '{}'.", + name, + dictionaryObject.getClass() + ); + } + return null; + } + + @Override + public Long getObjectNumber(String name) { + COSBase dictionaryObject = wrapped.getItem(name); + if (dictionaryObject instanceof COSObject) { + return ((COSObject) dictionaryObject).getObjectNumber(); + } + return null; + } + + @Override + public byte[] getStreamBytes() throws IOException { + if (wrapped instanceof COSStream) { + try (InputStream is = ((COSStream) wrapped).createInputStream()) { + return Utils.toByteArray(is); + } + } + return null; + } + + @Override + public String toString() { + return wrapped.toString(); + } +} diff --git a/src/main/java/it/pagopa/dss/SignatureService.java b/src/main/java/it/pagopa/dss/SignatureService.java new file mode 100644 index 0000000..19ed076 --- /dev/null +++ b/src/main/java/it/pagopa/dss/SignatureService.java @@ -0,0 +1,14 @@ +package it.pagopa.dss; + +public final class SignatureService { + + private SignatureService() {} + + /** + * Create and get the interface to access the signature service + * @return SignatureServiceInterface + */ + public static SignatureServiceInterface getInterface() { + return new SignatureServiceImpl(); + } +} diff --git a/src/main/java/it/pagopa/dss/SignatureServiceImpl.java b/src/main/java/it/pagopa/dss/SignatureServiceImpl.java new file mode 100644 index 0000000..b58f603 --- /dev/null +++ b/src/main/java/it/pagopa/dss/SignatureServiceImpl.java @@ -0,0 +1,126 @@ +package it.pagopa.dss; + +import eu.europa.esig.dss.enumerations.DigestAlgorithm; +import eu.europa.esig.dss.model.DSSDocument; +import eu.europa.esig.dss.model.FileDocument; +import eu.europa.esig.dss.pades.PAdESSignatureParameters; +import eu.europa.esig.dss.pades.SignatureFieldParameters; +import eu.europa.esig.dss.pades.SignatureImageParameters; +import eu.europa.esig.dss.pades.SignatureImageTextParameters; +import eu.europa.esig.dss.pades.validation.ByteRange; +import eu.europa.esig.dss.pdf.IPdfObjFactory; +import eu.europa.esig.dss.pdf.PDFSignatureService; +import eu.europa.esig.dss.pdf.ServiceLoaderPdfObjFactory; +import eu.europa.esig.dss.pdf.pdfbox.visible.PdfBoxNativeFont; +import eu.europa.esig.dss.spi.DSSUtils; +import it.pagopa.dss.exception.SignatureServiceException; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import org.apache.commons.codec.binary.Hex; +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class SignatureServiceImpl implements SignatureServiceInterface { + + private static final Logger LOG = LoggerFactory.getLogger(SignatureServiceImpl.class); + private static final float DEFAULT_FONT_SIZE = 8; + + @Override + public void generatePadesFile(File originalPdf, OutputStream outputStream) + throws IOException { + generatePadesFile(originalPdf, outputStream, null, null); + } + + @Override + public void generatePadesFile( + File originalPdf, + OutputStream outputStream, + String signatureFieldId + ) throws IOException { + generatePadesFile(originalPdf, outputStream, signatureFieldId, null); + } + + @Override + public void generatePadesFile( + File originalPdf, + OutputStream outputStream, + String signatureFieldId, + String signatureText + ) throws IOException { + DSSDocument documentToSign = new FileDocument(originalPdf); + + PAdESSignatureParameters parameters = new PAdESSignatureParameters(); + parameters.setDigestAlgorithm(DigestAlgorithm.SHA256); + parameters.setGenerateTBSWithoutCertificate(true); + + IPdfObjFactory pdfObjFactory = new ServiceLoaderPdfObjFactory(); + PDFSignatureService pdfSignatureService = pdfObjFactory.newPAdESSignatureService(); + + if (signatureFieldId != null) { + SignatureImageParameters imageParameters = new SignatureImageParameters(); + SignatureFieldParameters fieldParameters = new SignatureFieldParameters(); + + fieldParameters.setFieldId(signatureFieldId); + imageParameters.setFieldParameters(fieldParameters); + + if (signatureText != null) { + SignatureImageTextParameters textParameters = new SignatureImageTextParameters(); + PdfBoxNativeFont font = new PdfBoxNativeFont(PDType1Font.HELVETICA); + font.setSize(DEFAULT_FONT_SIZE); + textParameters.setFont(font); + textParameters.setText(signatureText); + imageParameters.setTextParameters(textParameters); + } + + parameters.setImageParameters(imageParameters); + } else { + LOG.warn("signatureFieldId is null, new field created!"); + } + + final byte[] emptySignatureValue = DSSUtils.EMPTY_BYTE_ARRAY; + DSSDocument noSign = pdfSignatureService.sign( + documentToSign, + emptySignatureValue, + parameters + ); + noSign.writeTo(outputStream); + } + + @Override + public void addSignatureToPadesFile( + File padesPdf, + OutputStream outputStream, + String signatureFieldId, + byte[] signatureValue + ) throws IOException, SignatureServiceException { + DSSDocument documentToSign = new FileDocument(padesPdf); + ByteRange range = Utility.getByteRange(documentToSign, signatureFieldId); + + byte[] signatureHex = Hex.encodeHexString(signatureValue).getBytes(); + BufferedInputStream inputBuffer = new BufferedInputStream( + documentToSign.openStream() + ); + BufferedOutputStream outBuffer = new BufferedOutputStream(outputStream); + + int ch; + int i = 0; + + while ((ch = inputBuffer.read()) != -1) { + if ( + i > range.getFirstPartEnd() && i <= range.getFirstPartEnd() + signatureHex.length + ) { + ch = signatureHex[i - range.getFirstPartEnd() - 1]; + } + outBuffer.write(ch); + i += 1; + } + + outBuffer.close(); + inputBuffer.close(); + outputStream.close(); + } +} diff --git a/src/main/java/it/pagopa/dss/SignatureServiceInterface.java b/src/main/java/it/pagopa/dss/SignatureServiceInterface.java new file mode 100644 index 0000000..8a8f5e9 --- /dev/null +++ b/src/main/java/it/pagopa/dss/SignatureServiceInterface.java @@ -0,0 +1,63 @@ +package it.pagopa.dss; + +import it.pagopa.dss.exception.SignatureServiceException; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; + +public interface SignatureServiceInterface { + /** + * Add a PAdES structure to an input PDF and save it in an outputStream + * @param originalPdf pdf file to which to add the PAdES structure + * @param outputStream outputstream on which to write the PDF file with the added PAdES structure + * @return void + * @throws IOException + */ + void generatePadesFile(File originalPdf, OutputStream outputStream) throws IOException; + + /** + * Add a PAdES structure to an input PDF and save it in an outputStream + * @param originalPdf pdf file to which to add the PAdES structure + * @param outputStream outputstream on which to write the PDF file with the added PAdES structure + * @param signatureFieldId id of the signature field present on the PDF file + * @return void + * @throws IOException + */ + void generatePadesFile( + File originalPdf, + OutputStream outputStream, + String signatureFieldId + ) throws IOException; + + /** + * Add a PAdES structure to an input PDF and save it in an outputStream + * @param originalPdf pdf file to which to add the PAdES structure + * @param outputStream outputstream on which to write the PDF file with the added PAdES structure + * @param signatureFieldId id of the signature field present on the PDF file + * @param signatureText text to add to the signature field + * @return void + * @throws IOException + */ + void generatePadesFile( + File originalPdf, + OutputStream outputStream, + String signatureFieldId, + String signatureText + ) throws IOException; + + /** + * Adds a signed hash to a PAdES format file and saves it in an outputStream given in input + * @param padesPdf PDF file with related PAdES structure + * @param outputStream outputstream on which to write the PDF signed + * @param signatureFieldId id of the signature field present on the PDF file + * @param signatureValue byte array of the signature + * @return void + * @throws IOException + */ + void addSignatureToPadesFile( + File padesPdf, + OutputStream outputStream, + String signatureFieldId, + byte[] signatureValue + ) throws IOException, SignatureServiceException; +} diff --git a/src/main/java/it/pagopa/dss/Utility.java b/src/main/java/it/pagopa/dss/Utility.java new file mode 100644 index 0000000..5e96e6f --- /dev/null +++ b/src/main/java/it/pagopa/dss/Utility.java @@ -0,0 +1,82 @@ +package it.pagopa.dss; + +import eu.europa.esig.dss.model.DSSDocument; +import eu.europa.esig.dss.pades.validation.ByteRange; +import eu.europa.esig.dss.pdf.PAdESConstants; +import eu.europa.esig.dss.pdf.PdfArray; +import eu.europa.esig.dss.pdf.PdfDict; +import eu.europa.esig.dss.pdf.pdfbox.PdfBoxDocumentReader; +import eu.europa.esig.dss.utils.Utils; +import it.pagopa.dss.exception.SignatureServiceException; +import java.io.IOException; +import java.util.List; +import org.apache.pdfbox.cos.COSDictionary; +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.cos.COSObject; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class Utility { + + private Utility() {} + + private static final Logger LOG = LoggerFactory.getLogger(Utility.class); + + static ByteRange getByteRange(DSSDocument documentToSign, String signatureFieldId) + throws IOException, SignatureServiceException { + PdfBoxDocumentReader reader = new PdfBoxDocumentReader(documentToSign); + PDDocument pdDocument = reader.getPDDocument(); + List pdSignatureFields = pdDocument.getSignatureFields(); + + if (Utils.isCollectionNotEmpty(pdSignatureFields)) { + for (PDSignatureField signatureField : pdSignatureFields) { + if (signatureField.getFullyQualifiedName().equalsIgnoreCase(signatureFieldId)) { + COSObject sigDictObject = signatureField.getCOSObject().getCOSObject(COSName.V); + if ( + sigDictObject == null || !(sigDictObject.getObject() instanceof COSDictionary) + ) { + LOG.warn("Skip field '%s'", signatureField.getFullyQualifiedName()); + continue; + } + + PdfDict dictionary = new PdfBoxDict( + (COSDictionary) sigDictObject.getObject(), + pdDocument + ); + PdfArray byteRangeArray = dictionary.getAsArray(PAdESConstants.BYTE_RANGE_NAME); + + if (byteRangeArray == null) { + LOG.error( + "Unable to retrieve the '%s' field value.", + PAdESConstants.BYTE_RANGE_NAME + ); + throw new SignatureServiceException( + String.format( + "Unable to retrieve the '%s' field value.", + PAdESConstants.BYTE_RANGE_NAME + ) + ); + } + + int arraySize = byteRangeArray.size(); + int[] result = new int[arraySize]; + for (int i = 0; i < arraySize; i++) { + result[i] = byteRangeArray.getNumber(i).intValue(); + } + + ByteRange range = new ByteRange(result); + return range; + } + } + LOG.error("'%s' field not found!", signatureFieldId); + throw new SignatureServiceException( + String.format("'%s' field not found!", signatureFieldId) + ); + } else { + LOG.error("No signature fields found"); + throw new SignatureServiceException(String.format("No signature fields found")); + } + } +} diff --git a/src/main/java/it/pagopa/dss/exception/SignatureServiceException.java b/src/main/java/it/pagopa/dss/exception/SignatureServiceException.java new file mode 100644 index 0000000..727b1ea --- /dev/null +++ b/src/main/java/it/pagopa/dss/exception/SignatureServiceException.java @@ -0,0 +1,12 @@ +package it.pagopa.dss.exception; + +public class SignatureServiceException extends Exception { + + public SignatureServiceException(String errorMessage) { + super(errorMessage); + } + + public SignatureServiceException(String errorMessage, Throwable err) { + super(errorMessage, err); + } +} diff --git a/src/test/java/it/pagopa/dss/SignatureServiceTest.java b/src/test/java/it/pagopa/dss/SignatureServiceTest.java new file mode 100644 index 0000000..2e718ba --- /dev/null +++ b/src/test/java/it/pagopa/dss/SignatureServiceTest.java @@ -0,0 +1,36 @@ +package it.pagopa.dss; + +import static org.assertj.core.api.Assertions.assertThat; + +import eu.europa.esig.dss.model.DSSDocument; +import eu.europa.esig.dss.model.FileDocument; +import eu.europa.esig.dss.pades.validation.ByteRange; +import java.io.File; +import java.io.FileOutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; + +class SignatureServiceTest { + + private final SignatureServiceInterface serviceInterface = SignatureService.getInterface(); + private final String testFileName = "demo.pdf"; + private final String testFieldId = "Signature1"; + + @Test + public void assertByteRange() { + try { + Path tempFile = Files.createTempFile(null, null); + FileOutputStream fos = new FileOutputStream(tempFile.toFile()); + serviceInterface.generatePadesFile(new File(testFileName), fos, testFieldId); + + DSSDocument documentToSign = new FileDocument(tempFile.toFile()); + ByteRange range = Utility.getByteRange(documentToSign, testFieldId); + + assertThat(range.getFirstPartEnd()).isEqualTo(7622); + assertThat(range.getSecondPartStart()).isEqualTo(26568); + } catch (Exception e) { + e.printStackTrace(); + } + } +} From f5d092f226677f3a24f25f4d1f1149cf8e609305 Mon Sep 17 00:00:00 2001 From: grausof Date: Tue, 6 Sep 2022 12:22:19 +0200 Subject: [PATCH 02/25] Change codereview pipeline --- .github/workflows/code-review.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code-review.yml b/.github/workflows/code-review.yml index eae1297..f16d70a 100644 --- a/.github/workflows/code-review.yml +++ b/.github/workflows/code-review.yml @@ -5,7 +5,8 @@ name: Code Review Pipeline on: push: - branches: [ "main" ] + branches-ignore: + - main pull_request: branches: [ "main" ] @@ -29,7 +30,7 @@ jobs: - name: Prettier check run: mvn -B verify prettier:check - + - name: Checkstyle run: mvn -B verify checkstyle:checkstyle - uses: jwgmeligmeyling/checkstyle-github-action@master From 18110b0f4ed87d40c625da80b9389ecd375f145f Mon Sep 17 00:00:00 2001 From: grausof Date: Tue, 6 Sep 2022 12:23:02 +0200 Subject: [PATCH 03/25] Change codereview pipeline --- .github/workflows/code-review.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code-review.yml b/.github/workflows/code-review.yml index f16d70a..0c004b7 100644 --- a/.github/workflows/code-review.yml +++ b/.github/workflows/code-review.yml @@ -5,10 +5,10 @@ name: Code Review Pipeline on: push: + branches: [ "main" ] + pull_request: branches-ignore: - main - pull_request: - branches: [ "main" ] jobs: build: From aef3049cf82542aeee95b52bc6dabf9a6ff8d376 Mon Sep 17 00:00:00 2001 From: grausof Date: Tue, 6 Sep 2022 12:27:20 +0200 Subject: [PATCH 04/25] Change codereview pipeline --- .github/workflows/code-review.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/code-review.yml b/.github/workflows/code-review.yml index 0c004b7..5ee55d1 100644 --- a/.github/workflows/code-review.yml +++ b/.github/workflows/code-review.yml @@ -7,8 +7,7 @@ on: push: branches: [ "main" ] pull_request: - branches-ignore: - - main + branches: [ "main" ] jobs: build: From aa5a13a85510efe1907e1c1dd9ed47c39dee7699 Mon Sep 17 00:00:00 2001 From: grausof Date: Tue, 6 Sep 2022 12:35:35 +0200 Subject: [PATCH 05/25] Add comment on addSignature --- src/main/java/it/pagopa/dss/SignatureServiceImpl.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/it/pagopa/dss/SignatureServiceImpl.java b/src/main/java/it/pagopa/dss/SignatureServiceImpl.java index b58f603..54c84e3 100644 --- a/src/main/java/it/pagopa/dss/SignatureServiceImpl.java +++ b/src/main/java/it/pagopa/dss/SignatureServiceImpl.java @@ -109,6 +109,10 @@ public void addSignatureToPadesFile( int ch; int i = 0; + /* + * The value of the signature must be inserted in place of the placeholder present in the PAdES PDF. + * The starting byte of the placeholder is identified by range.getFirstPartEnd () + */ while ((ch = inputBuffer.read()) != -1) { if ( i > range.getFirstPartEnd() && i <= range.getFirstPartEnd() + signatureHex.length From 9957473779845aae88244dd85df08300ada73ca6 Mon Sep 17 00:00:00 2001 From: grausof Date: Tue, 6 Sep 2022 12:42:13 +0200 Subject: [PATCH 06/25] Bug fix --- src/main/java/it/pagopa/dss/PdfBoxArray.java | 4 +- src/main/java/it/pagopa/dss/Utility.java | 89 +++++++++++--------- 2 files changed, 50 insertions(+), 43 deletions(-) diff --git a/src/main/java/it/pagopa/dss/PdfBoxArray.java b/src/main/java/it/pagopa/dss/PdfBoxArray.java index e2d70b7..2914c77 100644 --- a/src/main/java/it/pagopa/dss/PdfBoxArray.java +++ b/src/main/java/it/pagopa/dss/PdfBoxArray.java @@ -75,7 +75,9 @@ private byte[] toBytes(COSBase val) throws IOException { ); } try (InputStream is = cosStream.createInputStream()) { - return DSSUtils.toByteArray(is); + byte[] result = DSSUtils.toByteArray(is); + cosStream.close(); + return result; } } diff --git a/src/main/java/it/pagopa/dss/Utility.java b/src/main/java/it/pagopa/dss/Utility.java index 5e96e6f..61fbf24 100644 --- a/src/main/java/it/pagopa/dss/Utility.java +++ b/src/main/java/it/pagopa/dss/Utility.java @@ -1,6 +1,7 @@ package it.pagopa.dss; import eu.europa.esig.dss.model.DSSDocument; +import eu.europa.esig.dss.pades.exception.InvalidPasswordException; import eu.europa.esig.dss.pades.validation.ByteRange; import eu.europa.esig.dss.pdf.PAdESConstants; import eu.europa.esig.dss.pdf.PdfArray; @@ -26,57 +27,61 @@ private Utility() {} static ByteRange getByteRange(DSSDocument documentToSign, String signatureFieldId) throws IOException, SignatureServiceException { - PdfBoxDocumentReader reader = new PdfBoxDocumentReader(documentToSign); - PDDocument pdDocument = reader.getPDDocument(); - List pdSignatureFields = pdDocument.getSignatureFields(); + try (PdfBoxDocumentReader reader = new PdfBoxDocumentReader(documentToSign)) { + PDDocument pdDocument = reader.getPDDocument(); + List pdSignatureFields = pdDocument.getSignatureFields(); - if (Utils.isCollectionNotEmpty(pdSignatureFields)) { - for (PDSignatureField signatureField : pdSignatureFields) { - if (signatureField.getFullyQualifiedName().equalsIgnoreCase(signatureFieldId)) { - COSObject sigDictObject = signatureField.getCOSObject().getCOSObject(COSName.V); - if ( - sigDictObject == null || !(sigDictObject.getObject() instanceof COSDictionary) - ) { - LOG.warn("Skip field '%s'", signatureField.getFullyQualifiedName()); - continue; - } - - PdfDict dictionary = new PdfBoxDict( - (COSDictionary) sigDictObject.getObject(), - pdDocument - ); - PdfArray byteRangeArray = dictionary.getAsArray(PAdESConstants.BYTE_RANGE_NAME); + if (Utils.isCollectionNotEmpty(pdSignatureFields)) { + for (PDSignatureField signatureField : pdSignatureFields) { + if (signatureField.getFullyQualifiedName().equalsIgnoreCase(signatureFieldId)) { + COSObject sigDictObject = signatureField.getCOSObject().getCOSObject(COSName.V); + if ( + sigDictObject == null || !(sigDictObject.getObject() instanceof COSDictionary) + ) { + LOG.warn("Skip field '%s'", signatureField.getFullyQualifiedName()); + continue; + } - if (byteRangeArray == null) { - LOG.error( - "Unable to retrieve the '%s' field value.", - PAdESConstants.BYTE_RANGE_NAME + PdfDict dictionary = new PdfBoxDict( + (COSDictionary) sigDictObject.getObject(), + pdDocument ); - throw new SignatureServiceException( - String.format( + PdfArray byteRangeArray = dictionary.getAsArray(PAdESConstants.BYTE_RANGE_NAME); + + if (byteRangeArray == null) { + LOG.error( "Unable to retrieve the '%s' field value.", PAdESConstants.BYTE_RANGE_NAME - ) - ); - } + ); + throw new SignatureServiceException( + String.format( + "Unable to retrieve the '%s' field value.", + PAdESConstants.BYTE_RANGE_NAME + ) + ); + } - int arraySize = byteRangeArray.size(); - int[] result = new int[arraySize]; - for (int i = 0; i < arraySize; i++) { - result[i] = byteRangeArray.getNumber(i).intValue(); - } + int arraySize = byteRangeArray.size(); + int[] result = new int[arraySize]; + for (int i = 0; i < arraySize; i++) { + result[i] = byteRangeArray.getNumber(i).intValue(); + } - ByteRange range = new ByteRange(result); - return range; + ByteRange range = new ByteRange(result); + return range; + } } + LOG.error("'%s' field not found!", signatureFieldId); + throw new SignatureServiceException( + String.format("'%s' field not found!", signatureFieldId) + ); + } else { + LOG.error("No signature fields found"); + throw new SignatureServiceException(String.format("No signature fields found")); } - LOG.error("'%s' field not found!", signatureFieldId); - throw new SignatureServiceException( - String.format("'%s' field not found!", signatureFieldId) - ); - } else { - LOG.error("No signature fields found"); - throw new SignatureServiceException(String.format("No signature fields found")); + } catch (InvalidPasswordException e) { + LOG.error("This file is protected by password!"); + throw new SignatureServiceException(String.format("This file is protected by password!")); } } } From f23c9b7378ee3ece64835190aad5163e65f47516 Mon Sep 17 00:00:00 2001 From: grausof Date: Tue, 6 Sep 2022 12:43:50 +0200 Subject: [PATCH 07/25] prettier --- src/main/java/it/pagopa/dss/Utility.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/it/pagopa/dss/Utility.java b/src/main/java/it/pagopa/dss/Utility.java index 61fbf24..e0ae06b 100644 --- a/src/main/java/it/pagopa/dss/Utility.java +++ b/src/main/java/it/pagopa/dss/Utility.java @@ -34,9 +34,12 @@ static ByteRange getByteRange(DSSDocument documentToSign, String signatureFieldI if (Utils.isCollectionNotEmpty(pdSignatureFields)) { for (PDSignatureField signatureField : pdSignatureFields) { if (signatureField.getFullyQualifiedName().equalsIgnoreCase(signatureFieldId)) { - COSObject sigDictObject = signatureField.getCOSObject().getCOSObject(COSName.V); + COSObject sigDictObject = signatureField + .getCOSObject() + .getCOSObject(COSName.V); if ( - sigDictObject == null || !(sigDictObject.getObject() instanceof COSDictionary) + sigDictObject == null || + !(sigDictObject.getObject() instanceof COSDictionary) ) { LOG.warn("Skip field '%s'", signatureField.getFullyQualifiedName()); continue; @@ -46,7 +49,9 @@ static ByteRange getByteRange(DSSDocument documentToSign, String signatureFieldI (COSDictionary) sigDictObject.getObject(), pdDocument ); - PdfArray byteRangeArray = dictionary.getAsArray(PAdESConstants.BYTE_RANGE_NAME); + PdfArray byteRangeArray = dictionary.getAsArray( + PAdESConstants.BYTE_RANGE_NAME + ); if (byteRangeArray == null) { LOG.error( @@ -81,7 +86,9 @@ static ByteRange getByteRange(DSSDocument documentToSign, String signatureFieldI } } catch (InvalidPasswordException e) { LOG.error("This file is protected by password!"); - throw new SignatureServiceException(String.format("This file is protected by password!")); + throw new SignatureServiceException( + String.format("This file is protected by password!") + ); } } } From 80140dcd81c5269a392f09d81af2c752d5a247c1 Mon Sep 17 00:00:00 2001 From: grausof Date: Tue, 6 Sep 2022 13:09:21 +0200 Subject: [PATCH 08/25] Update README --- README.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 51c3b89..0fd3b77 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ # Requirements The latest version of `java-digital-sign` has the following minimal requirements: -- Java 11 and higher (tested up to Java 17) for the build is required. +- Java 11 and higher (tested up to Java 17) for the build is required. - Maven 3.6 and higher; - Memory and Disk: see minimal requirements for the used JVM. In general the higher available is better; - Operating system: no specific requirements (tested on Windows and Linux). @@ -36,3 +36,30 @@ to compile the JAR with all dependencies: ``` mvn compile exec:java ``` + +## Usage + +Generate a PAdES PDF with a signature placeholder +```java +File inputFile = new File("input.pdf"); +File outputFile = new File("pades.pdf"); +String fieldId = "SignatureFieldId"; + +FileOutputStream fileOutputStream = new FileOutputStream(outputFile); +SignatureServiceInterface serviceInterface = SignatureService.getInterface(); + +serviceInterface.generatePadesFile(inputFile, fileOutputStream, fieldId); +``` + +Put a signatureValue in a PAdES PDF +```java +File inputFile = new File("pades.pdf"); +File outputFile = new File("signed.pdf"); +String fieldId = "SignatureFieldId"; +byte[] signatureValue = [.....]; + +FileOutputStream fileOutputStream = new FileOutputStream(outputFile); +SignatureServiceInterface serviceInterface = SignatureService.getInterface(); + +serviceInterface.addSignatureToPadesFile(inputFile, fileOutputStream, fieldId, signatureValue); +``` From 527bda6972c0fc0b30882d23f8f25c9266492d90 Mon Sep 17 00:00:00 2001 From: grausof Date: Tue, 6 Sep 2022 14:21:06 +0200 Subject: [PATCH 09/25] Remove operatorWrap from checkstyle --- checkstyle.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/checkstyle.xml b/checkstyle.xml index d017627..37c20f4 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -108,7 +108,6 @@ - From 4410b0788d50f1a6293e09930ce6731661e2d8f7 Mon Sep 17 00:00:00 2001 From: grausof Date: Tue, 6 Sep 2022 14:39:02 +0200 Subject: [PATCH 10/25] refactor --- src/main/java/it/pagopa/dss/SignatureServiceImpl.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/it/pagopa/dss/SignatureServiceImpl.java b/src/main/java/it/pagopa/dss/SignatureServiceImpl.java index 54c84e3..63b0327 100644 --- a/src/main/java/it/pagopa/dss/SignatureServiceImpl.java +++ b/src/main/java/it/pagopa/dss/SignatureServiceImpl.java @@ -111,13 +111,12 @@ public void addSignatureToPadesFile( /* * The value of the signature must be inserted in place of the placeholder present in the PAdES PDF. - * The starting byte of the placeholder is identified by range.getFirstPartEnd () + * The starting byte of the placeholder is identified by range.getFirstPartEnd() */ + int offset = range.getFirstPartEnd(); while ((ch = inputBuffer.read()) != -1) { - if ( - i > range.getFirstPartEnd() && i <= range.getFirstPartEnd() + signatureHex.length - ) { - ch = signatureHex[i - range.getFirstPartEnd() - 1]; + if (i > offset && i <= offset + signatureHex.length) { + ch = signatureHex[i - offset - 1]; } outBuffer.write(ch); i += 1; From e7df096ad40ec0dbf5b5ea6dbf5a2b71efda1954 Mon Sep 17 00:00:00 2001 From: grausof Date: Tue, 6 Sep 2022 18:26:16 +0200 Subject: [PATCH 11/25] Use specific version in github action --- .github/workflows/code-review.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code-review.yml b/.github/workflows/code-review.yml index 5ee55d1..c7b86d1 100644 --- a/.github/workflows/code-review.yml +++ b/.github/workflows/code-review.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - name: Set up JDK 11 uses: actions/setup-java@v3 @@ -32,6 +32,6 @@ jobs: - name: Checkstyle run: mvn -B verify checkstyle:checkstyle - - uses: jwgmeligmeyling/checkstyle-github-action@master + - uses: jwgmeligmeyling/checkstyle-github-action@a12be500c097a5cedab881d4785ef9b4a4d3ee6a with: path: '**/checkstyle-result.xml' From 0732551861856cdc313085a2cffcc59ea56edf92 Mon Sep 17 00:00:00 2001 From: grausof Date: Wed, 7 Sep 2022 10:52:34 +0200 Subject: [PATCH 12/25] Add comment on getByteRange --- README.md | 2 +- pom.xml | 14 +++++++++----- src/main/java/it/pagopa/dss/Utility.java | 17 +++++++++++++++-- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0fd3b77..53d25d6 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ mvn checkstyle:check to compile the JAR with all dependencies: ``` -mvn compile exec:java +mvn clean compile assembly:single ``` ## Usage diff --git a/pom.xml b/pom.xml index ef14222..1b26b9e 100644 --- a/pom.xml +++ b/pom.xml @@ -118,15 +118,19 @@ maven-assembly-plugin - - - fully.qualified.MainClass - - jar-with-dependencies + + + make-assembly + package + + single + + + diff --git a/src/main/java/it/pagopa/dss/Utility.java b/src/main/java/it/pagopa/dss/Utility.java index e0ae06b..e100a8f 100644 --- a/src/main/java/it/pagopa/dss/Utility.java +++ b/src/main/java/it/pagopa/dss/Utility.java @@ -25,9 +25,22 @@ private Utility() {} private static final Logger LOG = LoggerFactory.getLogger(Utility.class); - static ByteRange getByteRange(DSSDocument documentToSign, String signatureFieldId) + /** + * Given a PAdES document and a signatureFieldId, this function first checks if the PAdES structure + * for that particular signatureFieldId is present and then returns the associated ByteRange. + * + * The ByteRange inside a PAdES PDF defines the starting byte of the PDF, the number of bytes before the signature, + * the end byte of the signature and the number of bytes remaining until the end of the file. + * ByteRange is used also for digest computation. + * + * @param padesPDF PAdES PDF with signature + * @param signatureFieldId id of the signature field present on the PDF file + * @return SignatureServiceInterface + * + */ + static ByteRange getByteRange(DSSDocument padesPDF, String signatureFieldId) throws IOException, SignatureServiceException { - try (PdfBoxDocumentReader reader = new PdfBoxDocumentReader(documentToSign)) { + try (PdfBoxDocumentReader reader = new PdfBoxDocumentReader(padesPDF)) { PDDocument pdDocument = reader.getPDDocument(); List pdSignatureFields = pdDocument.getSignatureFields(); From 13a9203ef41bbdb659cb036e96577e2447294093 Mon Sep 17 00:00:00 2001 From: grausof Date: Wed, 7 Sep 2022 14:30:40 +0200 Subject: [PATCH 13/25] Add packaging instruction --- .github/workflows/code-review.yml | 2 +- README.md | 24 +++--- pom.xml | 82 ++++++++++++++----- .../pagopa/dss/SignatureServiceInterface.java | 4 - 4 files changed, 74 insertions(+), 38 deletions(-) diff --git a/.github/workflows/code-review.yml b/.github/workflows/code-review.yml index c7b86d1..32fae11 100644 --- a/.github/workflows/code-review.yml +++ b/.github/workflows/code-review.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - name: Set up JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@3c69e1510165c5aacae5625826cdf51001fe2723 with: java-version: '11' distribution: 'temurin' diff --git a/README.md b/README.md index 53d25d6..afc7b4e 100644 --- a/README.md +++ b/README.md @@ -13,31 +13,33 @@ The latest version of `java-digital-sign` has the following minimal requirements - Memory and Disk: see minimal requirements for the used JVM. In general the higher available is better; - Operating system: no specific requirements (tested on Windows and Linux). -# Build and usage +# Build and Usage A simple build of the `java-digital-sign` Maven project can be done with the following command: ``` mvn clean install ``` the package is complete with plugins for prettier and syntax check -### Prettier +### Validate ``` -mvn prettier:write +mvn validate ``` - -### Checkstyle -``` -mvn checkstyle:check -``` - ## Compile -to compile the JAR with all dependencies: +to compile the JAR ``` -mvn clean compile assembly:single +mvn clean package ``` ## Usage +Add dependecy in pom.xml +```xml + + it.pagopa.dss + java-digital-sign + 0.0.1 + +``` Generate a PAdES PDF with a signature placeholder ```java diff --git a/pom.xml b/pom.xml index 1b26b9e..d2c6e51 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,14 @@ + + + github + PagoPA Apache Maven Packages + https://maven.pkg.github.com/pagopa/java-digital-sign + + + Francesco Grauso @@ -115,24 +123,11 @@ - - maven-assembly-plugin - - - jar-with-dependencies - - - - - make-assembly - package - - single - - - - - + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + maven-clean-plugin 3.1.0 @@ -169,7 +164,27 @@ maven-project-info-reports-plugin 3.0.0 - + + + + + maven-assembly-plugin + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + org.apache.maven.plugins maven-checkstyle-plugin ${checkstyle-maven-plugin.version} @@ -212,9 +227,32 @@ - - - + + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + diff --git a/src/main/java/it/pagopa/dss/SignatureServiceInterface.java b/src/main/java/it/pagopa/dss/SignatureServiceInterface.java index 8a8f5e9..9e18f5a 100644 --- a/src/main/java/it/pagopa/dss/SignatureServiceInterface.java +++ b/src/main/java/it/pagopa/dss/SignatureServiceInterface.java @@ -10,7 +10,6 @@ public interface SignatureServiceInterface { * Add a PAdES structure to an input PDF and save it in an outputStream * @param originalPdf pdf file to which to add the PAdES structure * @param outputStream outputstream on which to write the PDF file with the added PAdES structure - * @return void * @throws IOException */ void generatePadesFile(File originalPdf, OutputStream outputStream) throws IOException; @@ -20,7 +19,6 @@ public interface SignatureServiceInterface { * @param originalPdf pdf file to which to add the PAdES structure * @param outputStream outputstream on which to write the PDF file with the added PAdES structure * @param signatureFieldId id of the signature field present on the PDF file - * @return void * @throws IOException */ void generatePadesFile( @@ -35,7 +33,6 @@ void generatePadesFile( * @param outputStream outputstream on which to write the PDF file with the added PAdES structure * @param signatureFieldId id of the signature field present on the PDF file * @param signatureText text to add to the signature field - * @return void * @throws IOException */ void generatePadesFile( @@ -51,7 +48,6 @@ void generatePadesFile( * @param outputStream outputstream on which to write the PDF signed * @param signatureFieldId id of the signature field present on the PDF file * @param signatureValue byte array of the signature - * @return void * @throws IOException */ void addSignatureToPadesFile( From 690a8febf2f552dbb7d745fbb77110b2e34fc861 Mon Sep 17 00:00:00 2001 From: grausof Date: Wed, 7 Sep 2022 15:07:09 +0200 Subject: [PATCH 14/25] Add assertSignature test --- .../it/pagopa/dss/SignatureServiceImpl.java | 10 ++++- .../pagopa/dss/SignatureServiceInterface.java | 4 +- .../it/pagopa/dss/SignatureServiceTest.java | 39 +++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/main/java/it/pagopa/dss/SignatureServiceImpl.java b/src/main/java/it/pagopa/dss/SignatureServiceImpl.java index 63b0327..939c9ca 100644 --- a/src/main/java/it/pagopa/dss/SignatureServiceImpl.java +++ b/src/main/java/it/pagopa/dss/SignatureServiceImpl.java @@ -95,12 +95,18 @@ public void addSignatureToPadesFile( File padesPdf, OutputStream outputStream, String signatureFieldId, - byte[] signatureValue + byte[] signatureValue, + boolean signatureHexEncoded ) throws IOException, SignatureServiceException { DSSDocument documentToSign = new FileDocument(padesPdf); ByteRange range = Utility.getByteRange(documentToSign, signatureFieldId); - byte[] signatureHex = Hex.encodeHexString(signatureValue).getBytes(); + byte[] signatureHex = signatureValue; + + if (!signatureHexEncoded) { + signatureHex = Hex.encodeHexString(signatureValue).getBytes(); + } + BufferedInputStream inputBuffer = new BufferedInputStream( documentToSign.openStream() ); diff --git a/src/main/java/it/pagopa/dss/SignatureServiceInterface.java b/src/main/java/it/pagopa/dss/SignatureServiceInterface.java index 9e18f5a..83d21ab 100644 --- a/src/main/java/it/pagopa/dss/SignatureServiceInterface.java +++ b/src/main/java/it/pagopa/dss/SignatureServiceInterface.java @@ -48,12 +48,14 @@ void generatePadesFile( * @param outputStream outputstream on which to write the PDF signed * @param signatureFieldId id of the signature field present on the PDF file * @param signatureValue byte array of the signature + * @param signatureHexEncoded true if signatureValue is already Hex encoded * @throws IOException */ void addSignatureToPadesFile( File padesPdf, OutputStream outputStream, String signatureFieldId, - byte[] signatureValue + byte[] signatureValue, + boolean signatureHexEncoded ) throws IOException, SignatureServiceException; } diff --git a/src/test/java/it/pagopa/dss/SignatureServiceTest.java b/src/test/java/it/pagopa/dss/SignatureServiceTest.java index 2e718ba..2ab9742 100644 --- a/src/test/java/it/pagopa/dss/SignatureServiceTest.java +++ b/src/test/java/it/pagopa/dss/SignatureServiceTest.java @@ -9,6 +9,8 @@ import java.io.FileOutputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; +import org.apache.commons.codec.binary.Hex; import org.junit.jupiter.api.Test; class SignatureServiceTest { @@ -33,4 +35,41 @@ public void assertByteRange() { e.printStackTrace(); } } + + @Test + public void assertSignature() { + try { + byte[] signatureValue = Hex + .encodeHexString("INVALIDSIGNATURE".getBytes()) + .getBytes(); + + Path tempPadesFile = Files.createTempFile(null, null); + FileOutputStream fosPades = new FileOutputStream(tempPadesFile.toFile()); + serviceInterface.generatePadesFile(new File(testFileName), fosPades, testFieldId); + + Path tempSignedFile = Files.createTempFile(null, null); + FileOutputStream fosSigned = new FileOutputStream(tempSignedFile.toFile()); + + serviceInterface.addSignatureToPadesFile( + tempPadesFile.toFile(), + fosSigned, + testFieldId, + signatureValue, + true + ); + + DSSDocument signedDocument = new FileDocument(tempSignedFile.toFile()); + ByteRange range = Utility.getByteRange(signedDocument, testFieldId); + + byte[] signedByte = Arrays.copyOfRange( + Files.readAllBytes(tempSignedFile), + range.getFirstPartEnd() + 1, + range.getFirstPartEnd() + signatureValue.length + 1 + ); + + assertThat(signedByte).isEqualTo(signatureValue); + } catch (Exception e) { + e.printStackTrace(); + } + } } From 084b378acb07db8af0a5d7ae091e31ac33263b33 Mon Sep 17 00:00:00 2001 From: grausof Date: Wed, 7 Sep 2022 15:24:38 +0200 Subject: [PATCH 15/25] Add jacoco --- .github/workflows/code-review.yml | 9 +++++++++ pom.xml | 21 ++++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/.github/workflows/code-review.yml b/.github/workflows/code-review.yml index 32fae11..17329f6 100644 --- a/.github/workflows/code-review.yml +++ b/.github/workflows/code-review.yml @@ -35,3 +35,12 @@ jobs: - uses: jwgmeligmeyling/checkstyle-github-action@a12be500c097a5cedab881d4785ef9b4a4d3ee6a with: path: '**/checkstyle-result.xml' + + - name: Test + run: mvn -B verify test + - uses: madrapps/jacoco-report@v1.2 + with: + paths: '**/site/jacoco/jacoco.xml' + token: ${{ secrets.GITHUB_TOKEN }} + min-coverage-overall: 40 + min-coverage-changed-files: 60 \ No newline at end of file diff --git a/pom.xml b/pom.xml index d2c6e51..5b87c67 100644 --- a/pom.xml +++ b/pom.xml @@ -167,6 +167,25 @@ + + org.jacoco + jacoco-maven-plugin + 0.8.8 + + + + prepare-agent + + + + report + prepare-package + + report + + + + maven-assembly-plugin @@ -184,7 +203,7 @@ - + org.apache.maven.plugins maven-checkstyle-plugin ${checkstyle-maven-plugin.version} From 8ee5650f6ef993f82b4f3043326645dc24b8a50e Mon Sep 17 00:00:00 2001 From: grausof Date: Wed, 7 Sep 2022 15:30:47 +0200 Subject: [PATCH 16/25] Bug fix --- .github/workflows/code-review.yml | 2 +- .../java/it/pagopa/dss/SignatureServiceTest.java | 12 ++---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/.github/workflows/code-review.yml b/.github/workflows/code-review.yml index 17329f6..a8ad05a 100644 --- a/.github/workflows/code-review.yml +++ b/.github/workflows/code-review.yml @@ -40,7 +40,7 @@ jobs: run: mvn -B verify test - uses: madrapps/jacoco-report@v1.2 with: - paths: '**/site/jacoco/jacoco.xml' + paths: ${{ github.workspace }}/target/site/jacoco/jacoco.xml token: ${{ secrets.GITHUB_TOKEN }} min-coverage-overall: 40 min-coverage-changed-files: 60 \ No newline at end of file diff --git a/src/test/java/it/pagopa/dss/SignatureServiceTest.java b/src/test/java/it/pagopa/dss/SignatureServiceTest.java index 2ab9742..1521404 100644 --- a/src/test/java/it/pagopa/dss/SignatureServiceTest.java +++ b/src/test/java/it/pagopa/dss/SignatureServiceTest.java @@ -20,8 +20,7 @@ class SignatureServiceTest { private final String testFieldId = "Signature1"; @Test - public void assertByteRange() { - try { + public void assertByteRange() throws Exception { Path tempFile = Files.createTempFile(null, null); FileOutputStream fos = new FileOutputStream(tempFile.toFile()); serviceInterface.generatePadesFile(new File(testFileName), fos, testFieldId); @@ -31,14 +30,10 @@ public void assertByteRange() { assertThat(range.getFirstPartEnd()).isEqualTo(7622); assertThat(range.getSecondPartStart()).isEqualTo(26568); - } catch (Exception e) { - e.printStackTrace(); - } } @Test - public void assertSignature() { - try { + public void assertSignature() throws Exception { byte[] signatureValue = Hex .encodeHexString("INVALIDSIGNATURE".getBytes()) .getBytes(); @@ -68,8 +63,5 @@ public void assertSignature() { ); assertThat(signedByte).isEqualTo(signatureValue); - } catch (Exception e) { - e.printStackTrace(); - } } } From 10f8f94d616fcf1a6893fa0d2be0d8675d7382ed Mon Sep 17 00:00:00 2001 From: grausof Date: Wed, 7 Sep 2022 15:38:18 +0200 Subject: [PATCH 17/25] Update pipeline --- .github/workflows/code-review.yml | 6 +- pom.xml | 6 ++ .../it/pagopa/dss/SignatureServiceTest.java | 58 +++++++++---------- 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/.github/workflows/code-review.yml b/.github/workflows/code-review.yml index a8ad05a..7a07c07 100644 --- a/.github/workflows/code-review.yml +++ b/.github/workflows/code-review.yml @@ -28,16 +28,16 @@ jobs: run: mvn -B package --file pom.xml - name: Prettier check - run: mvn -B verify prettier:check + run: mvn prettier:check - name: Checkstyle - run: mvn -B verify checkstyle:checkstyle + run: mvn checkstyle:checkstyle - uses: jwgmeligmeyling/checkstyle-github-action@a12be500c097a5cedab881d4785ef9b4a4d3ee6a with: path: '**/checkstyle-result.xml' - name: Test - run: mvn -B verify test + run: mvn test - uses: madrapps/jacoco-report@v1.2 with: paths: ${{ github.workspace }}/target/site/jacoco/jacoco.xml diff --git a/pom.xml b/pom.xml index 5b87c67..0560478 100644 --- a/pom.xml +++ b/pom.xml @@ -185,6 +185,12 @@ + + + it.pagopa.dss.PdfBoxDict* + it.pagopa.dss.PdfBoxArray* + + maven-assembly-plugin diff --git a/src/test/java/it/pagopa/dss/SignatureServiceTest.java b/src/test/java/it/pagopa/dss/SignatureServiceTest.java index 1521404..657ea94 100644 --- a/src/test/java/it/pagopa/dss/SignatureServiceTest.java +++ b/src/test/java/it/pagopa/dss/SignatureServiceTest.java @@ -21,47 +21,45 @@ class SignatureServiceTest { @Test public void assertByteRange() throws Exception { - Path tempFile = Files.createTempFile(null, null); - FileOutputStream fos = new FileOutputStream(tempFile.toFile()); - serviceInterface.generatePadesFile(new File(testFileName), fos, testFieldId); + Path tempFile = Files.createTempFile(null, null); + FileOutputStream fos = new FileOutputStream(tempFile.toFile()); + serviceInterface.generatePadesFile(new File(testFileName), fos, testFieldId); - DSSDocument documentToSign = new FileDocument(tempFile.toFile()); - ByteRange range = Utility.getByteRange(documentToSign, testFieldId); + DSSDocument documentToSign = new FileDocument(tempFile.toFile()); + ByteRange range = Utility.getByteRange(documentToSign, testFieldId); - assertThat(range.getFirstPartEnd()).isEqualTo(7622); - assertThat(range.getSecondPartStart()).isEqualTo(26568); + assertThat(range.getFirstPartEnd()).isEqualTo(7622); + assertThat(range.getSecondPartStart()).isEqualTo(26568); } @Test public void assertSignature() throws Exception { - byte[] signatureValue = Hex - .encodeHexString("INVALIDSIGNATURE".getBytes()) - .getBytes(); + byte[] signatureValue = Hex.encodeHexString("INVALIDSIGNATURE".getBytes()).getBytes(); - Path tempPadesFile = Files.createTempFile(null, null); - FileOutputStream fosPades = new FileOutputStream(tempPadesFile.toFile()); - serviceInterface.generatePadesFile(new File(testFileName), fosPades, testFieldId); + Path tempPadesFile = Files.createTempFile(null, null); + FileOutputStream fosPades = new FileOutputStream(tempPadesFile.toFile()); + serviceInterface.generatePadesFile(new File(testFileName), fosPades, testFieldId); - Path tempSignedFile = Files.createTempFile(null, null); - FileOutputStream fosSigned = new FileOutputStream(tempSignedFile.toFile()); + Path tempSignedFile = Files.createTempFile(null, null); + FileOutputStream fosSigned = new FileOutputStream(tempSignedFile.toFile()); - serviceInterface.addSignatureToPadesFile( - tempPadesFile.toFile(), - fosSigned, - testFieldId, - signatureValue, - true - ); + serviceInterface.addSignatureToPadesFile( + tempPadesFile.toFile(), + fosSigned, + testFieldId, + signatureValue, + true + ); - DSSDocument signedDocument = new FileDocument(tempSignedFile.toFile()); - ByteRange range = Utility.getByteRange(signedDocument, testFieldId); + DSSDocument signedDocument = new FileDocument(tempSignedFile.toFile()); + ByteRange range = Utility.getByteRange(signedDocument, testFieldId); - byte[] signedByte = Arrays.copyOfRange( - Files.readAllBytes(tempSignedFile), - range.getFirstPartEnd() + 1, - range.getFirstPartEnd() + signatureValue.length + 1 - ); + byte[] signedByte = Arrays.copyOfRange( + Files.readAllBytes(tempSignedFile), + range.getFirstPartEnd() + 1, + range.getFirstPartEnd() + signatureValue.length + 1 + ); - assertThat(signedByte).isEqualTo(signatureValue); + assertThat(signedByte).isEqualTo(signatureValue); } } From 6bb0f17a038049403dea31d17c89d5c2bab19f55 Mon Sep 17 00:00:00 2001 From: grausof Date: Wed, 7 Sep 2022 15:45:51 +0200 Subject: [PATCH 18/25] Add PDFBox test --- pom.xml | 6 --- .../java/it/pagopa/dss/PdfBoxArrayTest.java | 51 ++++++++++++++++++ .../java/it/pagopa/dss/PdfBoxDictTest.java | 52 +++++++++++++++++++ 3 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 src/test/java/it/pagopa/dss/PdfBoxArrayTest.java create mode 100644 src/test/java/it/pagopa/dss/PdfBoxDictTest.java diff --git a/pom.xml b/pom.xml index 0560478..5b87c67 100644 --- a/pom.xml +++ b/pom.xml @@ -185,12 +185,6 @@ - - - it.pagopa.dss.PdfBoxDict* - it.pagopa.dss.PdfBoxArray* - - maven-assembly-plugin diff --git a/src/test/java/it/pagopa/dss/PdfBoxArrayTest.java b/src/test/java/it/pagopa/dss/PdfBoxArrayTest.java new file mode 100644 index 0000000..8cd68f2 --- /dev/null +++ b/src/test/java/it/pagopa/dss/PdfBoxArrayTest.java @@ -0,0 +1,51 @@ +package it.pagopa.dss; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import org.apache.pdfbox.cos.COSArray; +import org.apache.pdfbox.cos.COSBase; +import org.apache.pdfbox.cos.COSFloat; +import org.apache.pdfbox.cos.COSInteger; +import org.apache.pdfbox.cos.COSNumber; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.junit.jupiter.api.Test; + +public class PdfBoxArrayTest { + + @Test + public void getLongValueTest() throws IOException { + COSArray cosArray = new COSArray(); + cosArray.add(COSInteger.get(123456789)); + + COSBase integer = cosArray.get(0); + assertNotNull(integer); + assertTrue(integer instanceof COSNumber); + + try (PDDocument pdDocument = new PDDocument()) { + PdfBoxArray pdfBoxDict = new PdfBoxArray(cosArray, pdDocument); + Number numberValue = pdfBoxDict.getNumber(0); + assertNotNull(numberValue); + assertEquals(123456789, numberValue.longValue()); + } + } + + @Test + public void getFloatValueTest() throws IOException { + COSArray cosArray = new COSArray(); + cosArray.add(COSFloat.get("1.23456789e8")); + + COSBase floatNumber = cosArray.get(0); + assertNotNull(floatNumber); + assertTrue(floatNumber instanceof COSFloat); + + try (PDDocument pdDocument = new PDDocument()) { + PdfBoxArray pdfBoxDict = new PdfBoxArray(cosArray, pdDocument); + Number numberValue = pdfBoxDict.getNumber(0); + assertNotNull(numberValue); + assertEquals(123456789f, numberValue.floatValue()); + } + } +} diff --git a/src/test/java/it/pagopa/dss/PdfBoxDictTest.java b/src/test/java/it/pagopa/dss/PdfBoxDictTest.java new file mode 100644 index 0000000..5a66e9f --- /dev/null +++ b/src/test/java/it/pagopa/dss/PdfBoxDictTest.java @@ -0,0 +1,52 @@ +package it.pagopa.dss; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import org.apache.pdfbox.cos.COSBase; +import org.apache.pdfbox.cos.COSDictionary; +import org.apache.pdfbox.cos.COSFloat; +import org.apache.pdfbox.cos.COSInteger; +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.cos.COSNumber; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.junit.jupiter.api.Test; + +public class PdfBoxDictTest { + + @Test + public void getLongValueTest() throws IOException { + COSDictionary cosDictionary = new COSDictionary(); + cosDictionary.setItem(COSName.getPDFName("Integer"), COSInteger.get(123456789)); + + COSBase integer = cosDictionary.getDictionaryObject(COSName.getPDFName("Integer")); + assertNotNull(integer); + assertTrue(integer instanceof COSNumber); + + try (PDDocument pdDocument = new PDDocument()) { + PdfBoxDict pdfBoxDict = new PdfBoxDict(cosDictionary, pdDocument); + Number numberValue = pdfBoxDict.getNumberValue("Integer"); + assertNotNull(numberValue); + assertEquals(123456789, numberValue.longValue()); + } + } + + @Test + public void getFloatValueTest() throws IOException { + COSDictionary cosDictionary = new COSDictionary(); + cosDictionary.setItem(COSName.getPDFName("Float"), COSFloat.get("1.23456789e8")); + + COSBase floatNumber = cosDictionary.getDictionaryObject(COSName.getPDFName("Float")); + assertNotNull(floatNumber); + assertTrue(floatNumber instanceof COSFloat); + + try (PDDocument pdDocument = new PDDocument()) { + PdfBoxDict pdfBoxDict = new PdfBoxDict(cosDictionary, pdDocument); + Number numberValue = pdfBoxDict.getNumberValue("Float"); + assertNotNull(numberValue); + assertEquals(123456789f, numberValue.floatValue()); + } + } +} From 4f0771ea4af9e482f312a22a59d0ec94fbd7446a Mon Sep 17 00:00:00 2001 From: grausof Date: Wed, 7 Sep 2022 15:50:58 +0200 Subject: [PATCH 19/25] Add test exception --- src/test/java/it/pagopa/dss/ExceptionTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/test/java/it/pagopa/dss/ExceptionTest.java diff --git a/src/test/java/it/pagopa/dss/ExceptionTest.java b/src/test/java/it/pagopa/dss/ExceptionTest.java new file mode 100644 index 0000000..130a20d --- /dev/null +++ b/src/test/java/it/pagopa/dss/ExceptionTest.java @@ -0,0 +1,16 @@ +package it.pagopa.dss; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +import it.pagopa.dss.exception.SignatureServiceException; + +public class ExceptionTest { + + @Test + public void assertException(){ + SignatureServiceException ex = new SignatureServiceException("This is an exception"); + assertNotNull(ex); + } +} From fd27f0356ed2c3caedbfe18305fc60f70325bd5f Mon Sep 17 00:00:00 2001 From: grausof Date: Wed, 7 Sep 2022 16:04:20 +0200 Subject: [PATCH 20/25] Add exception test --- src/test/java/it/pagopa/dss/ExceptionTest.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/test/java/it/pagopa/dss/ExceptionTest.java b/src/test/java/it/pagopa/dss/ExceptionTest.java index 130a20d..802fe4d 100644 --- a/src/test/java/it/pagopa/dss/ExceptionTest.java +++ b/src/test/java/it/pagopa/dss/ExceptionTest.java @@ -2,15 +2,19 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; -import org.junit.jupiter.api.Test; - import it.pagopa.dss.exception.SignatureServiceException; +import org.junit.jupiter.api.Test; public class ExceptionTest { @Test - public void assertException(){ + public void assertException() { SignatureServiceException ex = new SignatureServiceException("This is an exception"); + SignatureServiceException ex2 = new SignatureServiceException( + "This is an exception", + ex + ); assertNotNull(ex); + assertNotNull(ex2); } } From dbdfe52c7a768d4b089cef54a46aed7b5fe3882a Mon Sep 17 00:00:00 2001 From: grausof Date: Wed, 7 Sep 2022 16:28:45 +0200 Subject: [PATCH 21/25] Add test exception --- .github/workflows/code-review.yml | 2 +- .../pagopa/dss/SignatureServiceInterface.java | 11 +++++-- .../it/pagopa/dss/SignatureServiceTest.java | 33 ++++++++++++++++++- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/.github/workflows/code-review.yml b/.github/workflows/code-review.yml index 7a07c07..52db213 100644 --- a/.github/workflows/code-review.yml +++ b/.github/workflows/code-review.yml @@ -38,7 +38,7 @@ jobs: - name: Test run: mvn test - - uses: madrapps/jacoco-report@v1.2 + - uses: madrapps/jacoco-report@fd4800e8a81e21bdf373438e5918b975df041d15 with: paths: ${{ github.workspace }}/target/site/jacoco/jacoco.xml token: ${{ secrets.GITHUB_TOKEN }} diff --git a/src/main/java/it/pagopa/dss/SignatureServiceInterface.java b/src/main/java/it/pagopa/dss/SignatureServiceInterface.java index 83d21ab..008872f 100644 --- a/src/main/java/it/pagopa/dss/SignatureServiceInterface.java +++ b/src/main/java/it/pagopa/dss/SignatureServiceInterface.java @@ -11,8 +11,10 @@ public interface SignatureServiceInterface { * @param originalPdf pdf file to which to add the PAdES structure * @param outputStream outputstream on which to write the PDF file with the added PAdES structure * @throws IOException + * @throws SignatureServiceException */ - void generatePadesFile(File originalPdf, OutputStream outputStream) throws IOException; + void generatePadesFile(File originalPdf, OutputStream outputStream) + throws IOException, SignatureServiceException; /** * Add a PAdES structure to an input PDF and save it in an outputStream @@ -20,12 +22,13 @@ public interface SignatureServiceInterface { * @param outputStream outputstream on which to write the PDF file with the added PAdES structure * @param signatureFieldId id of the signature field present on the PDF file * @throws IOException + * @throws SignatureServiceException */ void generatePadesFile( File originalPdf, OutputStream outputStream, String signatureFieldId - ) throws IOException; + ) throws IOException, SignatureServiceException; /** * Add a PAdES structure to an input PDF and save it in an outputStream @@ -34,13 +37,14 @@ void generatePadesFile( * @param signatureFieldId id of the signature field present on the PDF file * @param signatureText text to add to the signature field * @throws IOException + * @throws SignatureServiceException */ void generatePadesFile( File originalPdf, OutputStream outputStream, String signatureFieldId, String signatureText - ) throws IOException; + ) throws IOException, SignatureServiceException; /** * Adds a signed hash to a PAdES format file and saves it in an outputStream given in input @@ -50,6 +54,7 @@ void generatePadesFile( * @param signatureValue byte array of the signature * @param signatureHexEncoded true if signatureValue is already Hex encoded * @throws IOException + * @throws SignatureServiceException */ void addSignatureToPadesFile( File padesPdf, diff --git a/src/test/java/it/pagopa/dss/SignatureServiceTest.java b/src/test/java/it/pagopa/dss/SignatureServiceTest.java index 657ea94..4d762fa 100644 --- a/src/test/java/it/pagopa/dss/SignatureServiceTest.java +++ b/src/test/java/it/pagopa/dss/SignatureServiceTest.java @@ -5,12 +5,15 @@ import eu.europa.esig.dss.model.DSSDocument; import eu.europa.esig.dss.model.FileDocument; import eu.europa.esig.dss.pades.validation.ByteRange; +import it.pagopa.dss.exception.SignatureServiceException; import java.io.File; import java.io.FileOutputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.security.SignatureException; import java.util.Arrays; import org.apache.commons.codec.binary.Hex; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class SignatureServiceTest { @@ -38,7 +41,12 @@ public void assertSignature() throws Exception { Path tempPadesFile = Files.createTempFile(null, null); FileOutputStream fosPades = new FileOutputStream(tempPadesFile.toFile()); - serviceInterface.generatePadesFile(new File(testFileName), fosPades, testFieldId); + serviceInterface.generatePadesFile( + new File(testFileName), + fosPades, + testFieldId, + "Signed by..." + ); Path tempSignedFile = Files.createTempFile(null, null); FileOutputStream fosSigned = new FileOutputStream(tempSignedFile.toFile()); @@ -62,4 +70,27 @@ public void assertSignature() throws Exception { assertThat(signedByte).isEqualTo(signatureValue); } + + @Test + public void assertException() throws Exception { + Path tempFile = Files.createTempFile(null, null); + FileOutputStream fos = new FileOutputStream(tempFile.toFile()); + + IllegalArgumentException thrown = Assertions.assertThrows( + IllegalArgumentException.class, + () -> { + serviceInterface.generatePadesFile( + new File(testFileName), + fos, + "thisFieldNotExist" + ); + }, + "IllegalArgumentException was expected" + ); + + Assertions.assertEquals( + "The signature field 'thisFieldNotExist' does not exist.", + thrown.getMessage() + ); + } } From 5c171d7d8d22df97285cc2b69bbf7f794d020c09 Mon Sep 17 00:00:00 2001 From: grausof Date: Fri, 9 Sep 2022 09:48:01 +0200 Subject: [PATCH 22/25] Add some comments --- src/main/java/it/pagopa/dss/SignatureServiceImpl.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/it/pagopa/dss/SignatureServiceImpl.java b/src/main/java/it/pagopa/dss/SignatureServiceImpl.java index 939c9ca..53cbe7a 100644 --- a/src/main/java/it/pagopa/dss/SignatureServiceImpl.java +++ b/src/main/java/it/pagopa/dss/SignatureServiceImpl.java @@ -51,16 +51,21 @@ public void generatePadesFile( String signatureFieldId, String signatureText ) throws IOException { + + //converting the file into a DSS document (from dss library) DSSDocument documentToSign = new FileDocument(originalPdf); + //initialization of PAdES parameters PAdESSignatureParameters parameters = new PAdESSignatureParameters(); parameters.setDigestAlgorithm(DigestAlgorithm.SHA256); parameters.setGenerateTBSWithoutCertificate(true); + //initialization of PAdES signature service on a PDF object IPdfObjFactory pdfObjFactory = new ServiceLoaderPdfObjFactory(); PDFSignatureService pdfSignatureService = pdfObjFactory.newPAdESSignatureService(); if (signatureFieldId != null) { + //initialization of the parameters to set the signature field to be used SignatureImageParameters imageParameters = new SignatureImageParameters(); SignatureFieldParameters fieldParameters = new SignatureFieldParameters(); @@ -68,6 +73,7 @@ public void generatePadesFile( imageParameters.setFieldParameters(fieldParameters); if (signatureText != null) { + //set the text to be added in the signature field with its font SignatureImageTextParameters textParameters = new SignatureImageTextParameters(); PdfBoxNativeFont font = new PdfBoxNativeFont(PDType1Font.HELVETICA); font.setSize(DEFAULT_FONT_SIZE); @@ -81,6 +87,7 @@ public void generatePadesFile( LOG.warn("signatureFieldId is null, new field created!"); } + //the PAdES PDF that is generated initially has a signature consisting of all 0's which is used as a placeholder final byte[] emptySignatureValue = DSSUtils.EMPTY_BYTE_ARRAY; DSSDocument noSign = pdfSignatureService.sign( documentToSign, @@ -103,6 +110,10 @@ public void addSignatureToPadesFile( byte[] signatureHex = signatureValue; + /* + * The signature in order to be inserted inside a PAdES PDF must be HEX encoded. + * ETSI EN 319 142-1 + */ if (!signatureHexEncoded) { signatureHex = Hex.encodeHexString(signatureValue).getBytes(); } From b5927ede1805a101ed80caa4b9e52d0939084ae4 Mon Sep 17 00:00:00 2001 From: Francesco Grauso Date: Fri, 9 Sep 2022 13:19:49 +0200 Subject: [PATCH 23/25] Update src/main/java/it/pagopa/dss/SignatureServiceImpl.java Co-authored-by: Danilo Spinelli --- src/main/java/it/pagopa/dss/SignatureServiceImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/it/pagopa/dss/SignatureServiceImpl.java b/src/main/java/it/pagopa/dss/SignatureServiceImpl.java index 53cbe7a..c815cf8 100644 --- a/src/main/java/it/pagopa/dss/SignatureServiceImpl.java +++ b/src/main/java/it/pagopa/dss/SignatureServiceImpl.java @@ -60,7 +60,6 @@ public void generatePadesFile( parameters.setDigestAlgorithm(DigestAlgorithm.SHA256); parameters.setGenerateTBSWithoutCertificate(true); - //initialization of PAdES signature service on a PDF object IPdfObjFactory pdfObjFactory = new ServiceLoaderPdfObjFactory(); PDFSignatureService pdfSignatureService = pdfObjFactory.newPAdESSignatureService(); From e865671d48002cc26b035a3264d20e737e4cd4b5 Mon Sep 17 00:00:00 2001 From: grausof Date: Fri, 9 Sep 2022 13:25:04 +0200 Subject: [PATCH 24/25] Add some comments --- src/main/java/it/pagopa/dss/SignatureServiceImpl.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/it/pagopa/dss/SignatureServiceImpl.java b/src/main/java/it/pagopa/dss/SignatureServiceImpl.java index c815cf8..77d9043 100644 --- a/src/main/java/it/pagopa/dss/SignatureServiceImpl.java +++ b/src/main/java/it/pagopa/dss/SignatureServiceImpl.java @@ -58,6 +58,12 @@ public void generatePadesFile( //initialization of PAdES parameters PAdESSignatureParameters parameters = new PAdESSignatureParameters(); parameters.setDigestAlgorithm(DigestAlgorithm.SHA256); + + /* + * there is a need to set it to true as in a first phase of creation of the PAdES + * a padding will be used for the signature therefore also the certificate will not be valid. + * If set to true the signature will be successful even with an invalid certificate + */ parameters.setGenerateTBSWithoutCertificate(true); IPdfObjFactory pdfObjFactory = new ServiceLoaderPdfObjFactory(); From 03005ad8318a4913ab70002c79922977458ecd37 Mon Sep 17 00:00:00 2001 From: Francesco Grauso Date: Fri, 9 Sep 2022 13:26:32 +0200 Subject: [PATCH 25/25] Update src/main/java/it/pagopa/dss/SignatureServiceImpl.java Co-authored-by: Danilo Spinelli --- src/main/java/it/pagopa/dss/SignatureServiceImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/it/pagopa/dss/SignatureServiceImpl.java b/src/main/java/it/pagopa/dss/SignatureServiceImpl.java index 77d9043..c699c29 100644 --- a/src/main/java/it/pagopa/dss/SignatureServiceImpl.java +++ b/src/main/java/it/pagopa/dss/SignatureServiceImpl.java @@ -70,7 +70,6 @@ public void generatePadesFile( PDFSignatureService pdfSignatureService = pdfObjFactory.newPAdESSignatureService(); if (signatureFieldId != null) { - //initialization of the parameters to set the signature field to be used SignatureImageParameters imageParameters = new SignatureImageParameters(); SignatureFieldParameters fieldParameters = new SignatureFieldParameters();