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 42 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
42 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
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
34 changes: 25 additions & 9 deletions src/main/java/digital/slovensko/autogram/core/Autogram.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
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;
Expand Down Expand Up @@ -56,22 +57,37 @@ public void checkPDFACompliance(SigningJob job) {
return;

ui.onWorkThreadDo(() -> {
var result = new PDFAStructureValidator().validate(job.getDocument());
// 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ý.

return;
}

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

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

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

public void wrapInWorkThread(Runnable callback) {
ui.onWorkThreadDo(callback);
}

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 Expand Up @@ -159,7 +175,7 @@ public void batchSign(SigningJob job, String batchId) {
ui.onWorkThreadDo(() -> {
try {
signCommonAndThen(job, batch.getSigningKey(), (jobNew) -> {
Logging.log("GUI: Signing batch job: " + job.hashCode() + " file " + job.getDocument().getName());
Logging.log("GUI: Signing batch job: " + job.hashCode() + " file " + job.getDocument().getDSSDocument().getName());
});
} catch (AutogramException e) {
job.onDocumentSignFailed(e);
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
69 changes: 36 additions & 33 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,8 +46,7 @@ public int getVisualizationWidth() {
}

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

Logging.log("Signing Job: " + this.hashCode() + " file " + getDocument().getName());
Logging.log("Signing Job: " + this.hashCode() + " file " + getDocument().getDSSDocument().getName());
boolean isContainer = getParameters().getContainer() != null;
var doc = switch (getParameters().getSignatureType()) {
case XAdES -> isContainer ? signDocumentAsAsiCWithXAdeS(key) : signDocumentAsXAdeS(key);
Expand All @@ -73,10 +72,10 @@ private DSSDocument signDocumentAsCAdeS(SigningKey key) {
signatureParameters.setCertificateChain(key.getCertificateChain());
signatureParameters.setSignWithExpiredCertificate(true);

var dataToSign = service.getDataToSign(getDocument(), signatureParameters);
var dataToSign = service.getDataToSign(getDocument().getDSSDocument(), signatureParameters);
var signatureValue = key.sign(dataToSign, jobParameters.getDigestAlgorithm());

return service.signDocument(getDocument(), signatureParameters, signatureValue);
return service.signDocument(getDocument().getDSSDocument(), signatureParameters, signatureValue);
}

private DSSDocument signDocumentAsAsiCWithXAdeS(SigningKey key) {
Expand All @@ -91,10 +90,10 @@ private DSSDocument signDocumentAsAsiCWithXAdeS(SigningKey key) {
if (signatureParameters.getSignatureLevel().equals(SignatureLevel.XAdES_BASELINE_T))
service.setTspSource(getParameters().getTspSource());

var dataToSign = service.getDataToSign(getDocument(), signatureParameters);
var dataToSign = service.getDataToSign(getDocument().getDSSDocument(), signatureParameters);
var signatureValue = key.sign(dataToSign, getParameters().getDigestAlgorithm());

return service.signDocument(getDocument(), signatureParameters, signatureValue);
return service.signDocument(getDocument().getDSSDocument(), signatureParameters, signatureValue);
}

private DSSDocument signDocumentAsXAdeS(SigningKey key) {
Expand All @@ -107,10 +106,10 @@ private DSSDocument signDocumentAsXAdeS(SigningKey key) {
signatureParameters.setCertificateChain(key.getCertificateChain());
signatureParameters.setSignWithExpiredCertificate(true);

var dataToSign = service.getDataToSign(getDocument(), signatureParameters);
var dataToSign = service.getDataToSign(getDocument().getDSSDocument(), signatureParameters);
var signatureValue = key.sign(dataToSign, jobParameters.getDigestAlgorithm());

return service.signDocument(getDocument(), signatureParameters, signatureValue);
return service.signDocument(getDocument().getDSSDocument(), signatureParameters, signatureValue);
}

private DSSDocument signDocumentAsASiCWithCAdeS(SigningKey key) {
Expand All @@ -126,10 +125,10 @@ private DSSDocument signDocumentAsASiCWithCAdeS(SigningKey key) {
if (signatureParameters.getSignatureLevel().equals(SignatureLevel.CAdES_BASELINE_T))
service.setTspSource(getParameters().getTspSource());

var dataToSign = service.getDataToSign(getDocument(), signatureParameters);
var dataToSign = service.getDataToSign(getDocument().getDSSDocument(), signatureParameters);
var signatureValue = key.sign(dataToSign, jobParameters.getDigestAlgorithm());

return service.signDocument(getDocument(), signatureParameters, signatureValue);
return service.signDocument(getDocument().getDSSDocument(), signatureParameters, signatureValue);
}

private DSSDocument signDocumentAsPAdeS(SigningKey key) {
Expand All @@ -141,19 +140,20 @@ 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());
signatureParameters.setContentSize(9472*2);
}

var dataToSign = service.getDataToSign(getDocument(), signatureParameters);
var dataToSign = service.getDataToSign(getDocument().getDSSDocument(), signatureParameters);
var signatureValue = key.sign(dataToSign, jobParameters.getDigestAlgorithm());

return service.signDocument(getDocument(), signatureParameters, signatureValue);
return service.signDocument(getDocument().getDSSDocument(), 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) {
private static SigningJob build(AutogramDocument autogramDocument, SigningParameters params, Responder responder) {
DSSDocument document = autogramDocument.getDSSDocument();
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,47 +181,49 @@ private static SigningJob build(DSSDocument document, SigningParameters params,
document.setName(getXdcfFilename(document.getName()));
}

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

public static SigningJob buildFromRequest(DSSDocument document, SigningParameters params, Responder responder) {
public static SigningJob buildFromRequest(AutogramDocument 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) {
public static SigningJob buildFromFile(File file, Autogram autogram, Responder responder, boolean checkPDFACompliance, SignatureLevel signatureType, boolean isEn319132, TSPSource tspSource, boolean plainXmlEnabled) {
var document = createDSSFileDocumentFromFile(file);
autogram.handleProtectedPdfDocument(document);

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

private static SigningParameters getParametersForFile(FileDocument document, boolean checkPDFACompliance, SignatureLevel signatureType, boolean isEn319132, TSPSource tspSource, boolean plainXmlEnabled) {
private 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:
return SigningParameters.buildForPDF(document, checkPDFACompliance, isEn319132, tspSource);
return SigningParameters.buildForPDF(document.getDSSDocument(), checkPDFACompliance, isEn319132, tspSource);
case XAdES:
return SigningParameters.buildForASiCWithXAdES(document, checkPDFACompliance, isEn319132, tspSource, plainXmlEnabled);
return SigningParameters.buildForASiCWithXAdES(document.getDSSDocument(), checkPDFACompliance, isEn319132, tspSource, plainXmlEnabled);
case CAdES:
return SigningParameters.buildForASiCWithCAdES(document, checkPDFACompliance, isEn319132, tspSource, plainXmlEnabled);
return SigningParameters.buildForASiCWithCAdES(document.getDSSDocument(), checkPDFACompliance, isEn319132, tspSource, plainXmlEnabled);
default:
;
}

if (isPDF(document.getMimeType())) switch (signatureType) {
if (isPDF(document.getDSSDocument().getMimeType())) switch (signatureType) {
case PAdES_BASELINE_B:
return SigningParameters.buildForPDF(document, checkPDFACompliance, isEn319132, tspSource);
return SigningParameters.buildForPDF(document.getDSSDocument(), checkPDFACompliance, isEn319132, tspSource);
case XAdES_BASELINE_B:
return SigningParameters.buildForASiCWithXAdES(document, checkPDFACompliance, isEn319132, tspSource, plainXmlEnabled);
return SigningParameters.buildForASiCWithXAdES(document.getDSSDocument(), checkPDFACompliance, isEn319132, tspSource, plainXmlEnabled);
case CAdES_BASELINE_B:
return SigningParameters.buildForASiCWithCAdES(document, checkPDFACompliance, isEn319132, tspSource, plainXmlEnabled);
return SigningParameters.buildForASiCWithCAdES(document.getDSSDocument(), checkPDFACompliance, isEn319132, tspSource, plainXmlEnabled);
default:
;
}

return SigningParameters.buildForASiCWithXAdES(document, checkPDFACompliance, isEn319132, tspSource, plainXmlEnabled);
return SigningParameters.buildForASiCWithXAdES(document.getDSSDocument(), checkPDFACompliance, isEn319132, tspSource, plainXmlEnabled);
}

public boolean shouldCheckPDFCompliance() {
return parameters.getCheckPDFACompliance() && isPDF(document.getMimeType());
return parameters.getCheckPDFACompliance() && isPDF(document.getDSSDocument().getMimeType());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import digital.slovensko.autogram.core.errors.AutogramException;
import digital.slovensko.autogram.core.errors.SigningParametersException;
import digital.slovensko.autogram.core.errors.UnknownEformException;
import digital.slovensko.autogram.model.AutogramDocument;
import digital.slovensko.autogram.util.AsicContainerUtils;
import digital.slovensko.autogram.core.eforms.EFormUtils;
import digital.slovensko.autogram.core.eforms.xdc.XDCValidator;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
import static digital.slovensko.autogram.util.XMLUtils.getSecureDocumentBuilder;

import eu.europa.esig.dss.enumerations.DigestAlgorithm;

import eu.europa.esig.dss.model.DSSDocument;
import eu.europa.esig.dss.model.InMemoryDocument;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
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);
}
}
Loading
Loading