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

feat: sign protected pdfs #498

Open
wants to merge 51 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
ff84ad3
feat: initial implementation of signing protected pdfs
xhyrom Aug 19, 2024
6d00beb
feat: allow protected pdfs in batch
xhyrom Aug 19, 2024
84a311f
fix: proxy asic original document instead casting
xhyrom Aug 19, 2024
6eed34d
fix: tests
xhyrom Aug 19, 2024
aad94b7
fix: run callback, default pass
xhyrom Aug 20, 2024
bb7c5a1
fix: check if creating signature field is possible
xhyrom Aug 20, 2024
acb36d4
refactor: change place where we're asking for password
xhyrom Aug 21, 2024
11ef8b9
fix: tests
xhyrom Aug 22, 2024
553dd41
refactor: autogram document, split passwords
xhyrom Sep 2, 2024
9d07e15
add comment explaining PDF/A encryption
xhyrom Sep 2, 2024
83bd4df
feat: show document name while unlocking
xhyrom Sep 2, 2024
e74a283
fix: tests
xhyrom Sep 3, 2024
065f674
fix: pasword controller constructor
xhyrom Sep 25, 2024
56cc58a
fix: document name can be empty
xhyrom Sep 25, 2024
2778cb2
fix: handle protected pdf in sign endpoint before validation
xhyrom Sep 25, 2024
f8a4f77
fix: password dialog
xhyrom Oct 9, 2024
a8770c1
refactor: AutogramDocument#getDSSDocument
xhyrom Oct 9, 2024
a8a2b5d
refactor: remove Autogram from buildFromRequest
xhyrom Oct 9, 2024
cf8473c
fix: PDF/A compliant check
xhyrom Oct 9, 2024
ff4eca7
refactor: wrap to hasOpenDocumentPassword
xhyrom Oct 9, 2024
3c03aef
fix: errors
xhyrom Oct 9, 2024
2940dff
style: change invalid pass subheading
xhyrom Oct 9, 2024
762b7b9
fix :tests
xhyrom Oct 13, 2024
72d3337
revert: revert some modifiers
xhyrom Oct 13, 2024
39279d1
refactor(AutogramDocument): dont use this
xhyrom Oct 26, 2024
d6660e1
mv buildSigningJobFromFile to Autogram
celuchmarek Nov 15, 2024
0a38cb2
Revert "mv buildSigningJobFromFile to Autogram"
celuchmarek Nov 15, 2024
1a51bca
Revert "Revert "mv buildSigningJobFromFile to Autogram""
celuchmarek Nov 16, 2024
d936977
lock ubuntu runner version
celuchmarek Nov 16, 2024
6f99e22
bump gh actions
celuchmarek Nov 16, 2024
a5d5899
temp rm cache from gh actions
celuchmarek Nov 16, 2024
7d94479
bump jdk minor version
celuchmarek Nov 16, 2024
1cc94b5
use system jdk in github actions
celuchmarek Nov 16, 2024
6c15d52
use debug logging in gh actions
celuchmarek Nov 16, 2024
e587086
commit classpath
celuchmarek Nov 16, 2024
5a1a71e
add full stack trace gh actions
celuchmarek Nov 16, 2024
0df85f7
ignore classpath
celuchmarek Nov 16, 2024
b846984
Merge branch 'main' into feat/sign-protected-pdfs
celuchmarek Nov 16, 2024
eebceae
Revert "use system jdk in github actions"
celuchmarek Nov 16, 2024
5266922
revert liberica version
celuchmarek Nov 16, 2024
c064992
fix compilation error
celuchmarek Nov 17, 2024
a0a9e42
make test pipeline faster
celuchmarek Nov 17, 2024
b69a83b
refactor: cleanup
xhyrom Dec 18, 2024
577cd03
refactor: cleanup
xhyrom Dec 18, 2024
d230231
refactor: cleanup
xhyrom Dec 18, 2024
10bd450
refactor: cleanup
xhyrom Dec 18, 2024
91dd90d
refactor: cleanup
xhyrom Dec 18, 2024
5b6c2a8
revert: test ci
xhyrom Dec 18, 2024
9e84caf
feat: make autogram document implements dssdocument
xhyrom Dec 18, 2024
5803279
feat: make autogram document implements dssdocument
xhyrom Dec 18, 2024
6b0d033
refactor: cleanup
xhyrom Dec 18, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,4 @@ jobs:
with:
name: code-coverage-report-markdown
path: ./*/coverage-results.md
retention-days: 1
retention-days: 1
39 changes: 32 additions & 7 deletions src/main/java/digital/slovensko/autogram/core/Autogram.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import digital.slovensko.autogram.core.visualization.DocumentVisualizationBuilder;
import digital.slovensko.autogram.core.visualization.UnsupportedVisualization;
import digital.slovensko.autogram.drivers.TokenDriver;
import digital.slovensko.autogram.model.AutogramDocument;
import digital.slovensko.autogram.ui.BatchUiResult;
import digital.slovensko.autogram.ui.UI;
import digital.slovensko.autogram.util.Logging;
import digital.slovensko.autogram.util.PDFUtils;
import eu.europa.esig.dss.enumerations.SignatureLevel;
import eu.europa.esig.dss.model.DSSException;
import eu.europa.esig.dss.pdfa.PDFAStructureValidator;
import eu.europa.esig.dss.spi.x509.tsp.TSPSource;
Expand Down Expand Up @@ -56,22 +58,45 @@ public void checkPDFACompliance(SigningJob job) {
return;

ui.onWorkThreadDo(() -> {
// PDF/A doesn't support encryption
if (job.getDocument().hasOpenDocumentPassword()) {
ui.onUIThreadDo(() -> ui.onPDFAComplianceCheckFailed(job));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ešte uvažujem, či sa neplatí vyrobiť pre tento prípad inú hlášku. Prípadne vedieť do toho onPDFAComplianceCheckFailed poslať ešte AutogramException, na základe ktorej sa zobrazí taký alebo onaký text. Totiž, pri tomto hesle má zmysel povedať userovi konkrétne, že nie len že dokument nie je v súlade s PDF/A, ale je to práva preto, že je zaheslovaný.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Urcite ano. Poslime tam ui.onPDFPasswordProtectedCheck(job)

return;
}

var result = new PDFAStructureValidator().validate(job.getDocument());
if (!result.isCompliant()) {
ui.onUIThreadDo(() -> ui.onPDFAComplianceCheckFailed(job));
}
});
}

public void handleProtectedPdfDocument(AutogramDocument document) {
var protection = PDFUtils.determinePDFProtection(document);
if (protection == PDFUtils.PDFProtection.NONE)
return;

var password = ui.getDocumentPassword(document);
switch (protection) {
case OPEN_DOCUMENT_PASSWORD -> document.setOpenDocumentPassword(password);
case MASTER_PASSWORD -> document.setMasterPassword(password);
}
}

public SigningJob buildSigningJobFromFile(File file, Responder responder, boolean checkPDFACompliance, SignatureLevel signatureType, boolean isEn319132, TSPSource tspSource, boolean plainXmlEnabled) {
var document = SigningJob.createDSSFileDocumentFromFile(file);
handleProtectedPdfDocument(document);

var parameters = SigningJob.getParametersForFile(document, checkPDFACompliance, signatureType, isEn319132, tspSource, plainXmlEnabled);
return SigningJob.build(document, parameters, responder);
}

public void wrapInWorkThread(Runnable callback) {
ui.onWorkThreadDo(callback);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Toto sa pouziva presne na 1 mieste, cize dajme inline, nijako to nezvysuje prehladnost, skor naopak.

Copy link
Contributor Author

@xhyrom xhyrom Dec 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ui je private, preto som to spravil do autogram triedy priamo. Príde mi to elegantnejšie než robiť nejaký getUI keď sa všetko v UI robí iba v tejto classe.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ešte k tomu komentáru vyššie o PDF/A enkrypcií, tak nikdy mi to nešlo a podľa https://en.wikipedia.org/wiki/PDF/A#:~:text=PDF/A%20differs%20from%20PDF%20by%20prohibiting%20features%20unsuitable%20for%20long%2Dterm%20archiving%2C%20such%20as%20font%20linking%20(as%20opposed%20to%20font%20embedding)%20and%20encryption to nie je povolené. Mám to aj tak nechať prejsť kontrolou než to takto okamžite nechať failnúť?

}

public void startVisualization(SigningJob job) {
ui.onWorkThreadDo(() -> {
if (PDFUtils.isPdfAndPasswordProtected(job.getDocument())) {
ui.onUIThreadDo(() -> {
ui.showError(new AutogramException("Nastala chyba", "Dokument je chránený heslom", "Snažíte sa podpísať dokument chránený heslom, čo je funkcionalita, ktorá nie je podporovaná.\n\nOdstráňte ochranu heslom a potom budete môcť dokument podpísať."));
});
return;
}

try {
var visualization = DocumentVisualizationBuilder.fromJob(job, settings);
ui.onUIThreadDo(() -> ui.showVisualization(visualization, this));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import eu.europa.esig.dss.enumerations.ASiCContainerType;
import digital.slovensko.autogram.model.AutogramDocument;
import eu.europa.esig.dss.simplereport.SimpleReport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -24,7 +24,6 @@

import digital.slovensko.autogram.util.XMLUtils;
import eu.europa.esig.dss.enumerations.SignatureLevel;
import eu.europa.esig.dss.model.DSSDocument;
import eu.europa.esig.dss.model.DSSException;
import eu.europa.esig.dss.service.crl.OnlineCRLSource;
import eu.europa.esig.dss.service.http.commons.CommonsDataLoader;
Expand Down Expand Up @@ -167,7 +166,7 @@ public static ValidationReports getSignatureCheckReport(SigningJob job) {
return new ValidationReports(validator.validateDocument(), job);
}

public static SimpleReport getSignedDocumentSimpleReport(DSSDocument document) {
public static SimpleReport getSignedDocumentSimpleReport(AutogramDocument document) {
var validator = createDocumentValidator(document);
if (validator == null)
return null;
Expand Down
33 changes: 12 additions & 21 deletions src/main/java/digital/slovensko/autogram/core/SigningJob.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
import digital.slovensko.autogram.core.eforms.xdc.XDCBuilder;
import digital.slovensko.autogram.core.eforms.xdc.XDCValidator;
import digital.slovensko.autogram.core.errors.AutogramException;
import digital.slovensko.autogram.model.AutogramDocument;
import digital.slovensko.autogram.util.Logging;
import eu.europa.esig.dss.asic.cades.signature.ASiCWithCAdESService;
import eu.europa.esig.dss.asic.xades.signature.ASiCWithXAdESService;
import eu.europa.esig.dss.cades.signature.CAdESService;
import eu.europa.esig.dss.enumerations.MimeTypeEnum;
import eu.europa.esig.dss.enumerations.SignatureLevel;
import eu.europa.esig.dss.model.DSSDocument;
import eu.europa.esig.dss.model.FileDocument;
Expand All @@ -24,16 +24,16 @@

public class SigningJob {
private final Responder responder;
private final DSSDocument document;
private final AutogramDocument document;
private final SigningParameters parameters;

private SigningJob(DSSDocument document, SigningParameters parameters, Responder responder) {
private SigningJob(AutogramDocument document, SigningParameters parameters, Responder responder) {
this.document = document;
this.parameters = parameters;
this.responder = responder;
}

public DSSDocument getDocument() {
public AutogramDocument getDocument() {
return this.document;
}

Expand All @@ -46,7 +46,6 @@ public int getVisualizationWidth() {
}

public void signWithKeyAndRespond(SigningKey key) throws InterruptedException, AutogramException {

Logging.log("Signing Job: " + this.hashCode() + " file " + getDocument().getName());
boolean isContainer = getParameters().getContainer() != null;
var doc = switch (getParameters().getSignatureType()) {
Expand Down Expand Up @@ -141,6 +140,7 @@ private DSSDocument signDocumentAsPAdeS(SigningKey key) {
signatureParameters.setSigningCertificate(key.getCertificate());
signatureParameters.setCertificateChain(key.getCertificateChain());
signatureParameters.setSignWithExpiredCertificate(true);
signatureParameters.setPasswordProtection(document.getSigningPassword());

if (signatureParameters.getSignatureLevel().equals(SignatureLevel.PAdES_BASELINE_T)) {
service.setTspSource(getParameters().getTspSource());
Expand All @@ -153,7 +153,7 @@ private DSSDocument signDocumentAsPAdeS(SigningKey key) {
return service.signDocument(getDocument(), signatureParameters, signatureValue);
}

public static FileDocument createDSSFileDocumentFromFile(File file) {
public static AutogramDocument createDSSFileDocumentFromFile(File file) {
var fileDocument = new FileDocument(file);

if (fileDocument.getName().endsWith(".xdcf"))
Expand All @@ -165,12 +165,13 @@ else if (isXDC(fileDocument.getMimeType()) || isXML(fileDocument.getMimeType())
else if (isTxt(fileDocument.getMimeType()))
fileDocument.setMimeType(AutogramMimeType.TEXT_WITH_CHARSET);

return fileDocument;
return new AutogramDocument(fileDocument);
}

private static SigningJob build(DSSDocument document, SigningParameters params, Responder responder) {
public static SigningJob build(AutogramDocument autogramDocument, SigningParameters params, Responder responder) {
DSSDocument document = autogramDocument;
if (params.shouldCreateXdc() && !isXDC(document.getMimeType()) && !isAsice(document.getMimeType()))
document = XDCBuilder.transform(params, document.getName(), EFormUtils.getXmlFromDocument(document));
autogramDocument = new AutogramDocument(XDCBuilder.transform(params, document.getName(), EFormUtils.getXmlFromDocument(document)));

if (isTxt(document.getMimeType()))
document.setMimeType(AutogramMimeType.TEXT_WITH_CHARSET);
Expand All @@ -180,20 +181,10 @@ private static SigningJob build(DSSDocument document, SigningParameters params,
document.setName(getXdcfFilename(document.getName()));
}

return new SigningJob(document, params, responder);
}

public static SigningJob buildFromRequest(DSSDocument document, SigningParameters params, Responder responder) {
return build(document, params, responder);
}

public static SigningJob buildFromFile(File file, Responder responder, boolean checkPDFACompliance, SignatureLevel signatureType, boolean isEn319132, TSPSource tspSource, boolean plainXmlEnabled) {
var document = createDSSFileDocumentFromFile(file);
var parameters = getParametersForFile(document, checkPDFACompliance, signatureType, isEn319132, tspSource, plainXmlEnabled);
return build(document, parameters, responder);
return new SigningJob(autogramDocument, params, responder);
}

private static SigningParameters getParametersForFile(FileDocument document, boolean checkPDFACompliance, SignatureLevel signatureType, boolean isEn319132, TSPSource tspSource, boolean plainXmlEnabled) {
public static SigningParameters getParametersForFile(AutogramDocument document, boolean checkPDFACompliance, SignatureLevel signatureType, boolean isEn319132, TSPSource tspSource, boolean plainXmlEnabled) {
var level = SignatureValidator.getSignedDocumentSignatureLevel(SignatureValidator.getSignedDocumentSimpleReport(document));
if (level != null) switch (level.getSignatureForm()) {
case PAdES:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ public static AutogramException createFromDSSException(DSSException e) {
return new TsaServerMisconfiguredException("Nie je nastavená žiadna adresa TSA servera. Skontrolujte nastavenia TSA servera.", cause);
} else if (cause instanceof IOException && (cause.getMessage().contains("The specified module could not be found") || cause.getMessage().contains("Zadaný modul sa nepodarilo"))) {
return new PkcsEidWindowsDllException(e);
} else if (cause instanceof eu.europa.esig.dss.pades.exception.InvalidPasswordException) {
return new InvalidPasswordException("Zadali ste nesprávne heslo");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package digital.slovensko.autogram.core.errors;

public class InvalidPasswordException extends AutogramException {
public InvalidPasswordException(String message) {
super("Nesprávne heslo", "Heslo je nesprávne", message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
import javax.xml.parsers.ParserConfigurationException;

import digital.slovensko.autogram.core.UserSettings;
import digital.slovensko.autogram.model.AutogramDocument;
import eu.europa.esig.dss.model.DSSDocument;

import org.xml.sax.SAXException;

import digital.slovensko.autogram.core.AutogramMimeType;
import static digital.slovensko.autogram.core.AutogramMimeType.*;
import digital.slovensko.autogram.core.SigningJob;
import digital.slovensko.autogram.core.SigningParameters;
Expand All @@ -22,10 +22,10 @@

public class DocumentVisualizationBuilder {

private final DSSDocument document;
private final AutogramDocument document;
private final SigningParameters parameters;

private DocumentVisualizationBuilder(DSSDocument document, SigningParameters parameters) {
private DocumentVisualizationBuilder(AutogramDocument document, SigningParameters parameters) {
this.document = document;
this.parameters = parameters;
}
Expand All @@ -44,7 +44,7 @@ private Visualization createVisualization(SigningJob job, UserSettings userSetti
var documentToDisplay = document;
if (isAsice(documentToDisplay.getMimeType())) {
try {
documentToDisplay = AsicContainerUtils.getOriginalDocument(document);
documentToDisplay = new AutogramDocument(AsicContainerUtils.getOriginalDocument(document));
} catch (AutogramException e) {
return new UnsupportedVisualization(job);
}
Expand Down Expand Up @@ -86,7 +86,7 @@ private boolean isTranformationAvailable(String transformation) {
return transformation != null;
}

private boolean isDocumentSupportingTransformation(DSSDocument document) {
private boolean isDocumentSupportingTransformation(AutogramDocument document) {
return isXDC(document.getMimeType()) || isXML(document.getMimeType());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,27 @@

import digital.slovensko.autogram.core.SigningJob;
import digital.slovensko.autogram.core.UserSettings;
import digital.slovensko.autogram.model.AutogramDocument;
import digital.slovensko.autogram.ui.Visualizer;
import eu.europa.esig.dss.model.DSSDocument;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;

import javax.imageio.ImageIO;

public class PDFVisualization extends Visualization {
private final DSSDocument document;
private final AutogramDocument document;
private final UserSettings settings;


public PDFVisualization(DSSDocument document, SigningJob job, UserSettings settings) {
public PDFVisualization(AutogramDocument document, SigningJob job, UserSettings settings) {
super(job);
this.document = document;
this.settings = settings;
}

private ArrayList<byte []> getPdfImages() throws IOException {
var pdfDocument = PDDocument.load(this.document.openStream());
var pdfDocument = PDDocument.load(this.document.openStream(), new String(this.document.getOpenDocumentPassword()));
var pdfRenderer = new PDFRenderer(pdfDocument);
var divs = new ArrayList<byte[]>();
for (int page = 0; page < pdfDocument.getNumberOfPages(); ++page) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package digital.slovensko.autogram.model;

import eu.europa.esig.dss.enumerations.DigestAlgorithm;
import eu.europa.esig.dss.enumerations.MimeType;
import eu.europa.esig.dss.model.DSSDocument;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class AutogramDocument implements DSSDocument {
private final DSSDocument document;

private char[] openDocumentPassword = new char[0];
private char[] masterPassword = new char[0];

public AutogramDocument(DSSDocument document) {
this.document = document;
}

public boolean hasOpenDocumentPassword() {
return openDocumentPassword.length > 0;
}

public char[] getOpenDocumentPassword() {
return openDocumentPassword;
}

public void setOpenDocumentPassword(char[] openDocumentPassword) {
this.openDocumentPassword = openDocumentPassword;
}

public char[] getMasterPassword() {
return masterPassword;
}

public void setMasterPassword(char[] masterPassword) {
this.masterPassword = masterPassword;
}

public char[] getSigningPassword() {
return hasOpenDocumentPassword() ? openDocumentPassword : masterPassword;
}

@Override
public String getName() {
return document.getName();
}

@Override
public void setName(String s) {
document.setName(s);
}

@Override
public MimeType getMimeType() {
return document.getMimeType();
}

@Override
public void setMimeType(MimeType mimeType) {
document.setMimeType(mimeType);
}

@Override
public void save(String s) throws IOException {
document.save(s);
}

@Override
public String getDigest(DigestAlgorithm digestAlgorithm) {
return document.getDigest(digestAlgorithm);
}

@Override
public InputStream openStream() {
return document.openStream();
}

@Override
public void writeTo(OutputStream outputStream) throws IOException {
document.writeTo(outputStream);
}

public DSSDocument getDSSDocument() {
return document;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ public SignEndpoint(Autogram autogram) {
public void handle(HttpExchange exchange) throws IOException {
try {
var body = EndpointUtils.loadFromJsonExchange(exchange, SignRequestBody.class);
autogram.handleProtectedPdfDocument(body.getDocument());

body.validateDocument();
body.validateSigningParameters();

celuchmarek marked this conversation as resolved.
Show resolved Hide resolved
var responder = body.getBatchId() == null ? new ServerResponder(exchange)
: new ResponderInBatch(new ServerResponder(exchange), autogram.getBatch(body.getBatchId()));
var job = SigningJob.buildFromRequest(body.getDocument(), body.getParameters(autogram.getTspSource(), autogram.isPlainXmlEnabled()), responder);
var job = SigningJob.build(body.getDocument(), body.getParameters(autogram.getTspSource(), autogram.isPlainXmlEnabled()), responder);

if (body.getBatchId() != null)
autogram.batchSign(job, body.getBatchId());
Expand Down
Loading
Loading