aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas <>2024-09-05 09:55:35 +0200
committerThomas <>2024-09-05 09:55:35 +0200
commitd46ce27508d11be00ce247457f30e5381e7d280b (patch)
tree7621bc717a798d1889439064c062ec29cd2d45d9
parentdbdee1a6a73053602d4f8ff0817ac3e9ecf7911f (diff)
downloadpdf-as-4-d46ce27508d11be00ce247457f30e5381e7d280b.tar.gz
pdf-as-4-d46ce27508d11be00ce247457f30e5381e7d280b.tar.bz2
pdf-as-4-d46ce27508d11be00ce247457f30e5381e7d280b.zip
fix(verify): broken signature verification if documents contains a signature-field that was not used yet
-rw-r--r--pdf-as-common/src/main/java/at/gv/egiz/pdfas/common/settings/IProfileConstants.java2
-rw-r--r--pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/verify/pdfbox2/PDFBOXVerifier.java173
-rw-r--r--pdf-as-web/build.gradle1
-rw-r--r--pdf-as-web/src/test/java/at/gv/egiz/pdfas/web/test/SimpleVerifyServletTest.java164
-rw-r--r--pdf-as-web/src/test/resources/data/dummy-pdf-signed.pdfbin0 -> 167548 bytes
-rw-r--r--pdf-as-web/src/test/resources/data/placeholder_sigfield_and_qr.pdfbin0 -> 181919 bytes
6 files changed, 272 insertions, 68 deletions
diff --git a/pdf-as-common/src/main/java/at/gv/egiz/pdfas/common/settings/IProfileConstants.java b/pdf-as-common/src/main/java/at/gv/egiz/pdfas/common/settings/IProfileConstants.java
index 95eaa8ea..5adf320f 100644
--- a/pdf-as-common/src/main/java/at/gv/egiz/pdfas/common/settings/IProfileConstants.java
+++ b/pdf-as-common/src/main/java/at/gv/egiz/pdfas/common/settings/IProfileConstants.java
@@ -92,7 +92,7 @@ public interface IProfileConstants {
public final static String SIGNFIELD_VALUE = "adobeSignFieldValue";
public final static String TIMEZONE_BASE = "timezone";
public final static String SIG_PDFA1B_VALID = "SIG_PDFA1B_VALID";
- public final static String SIG_PDFA_VALID = "SIG_PDFA_VALID";
+ public final static String SIG_PDF A_VALID = "SIG_PDFA_VALID";
public final static String SIG_PDFUA_FORCE = "SIG_PDFUA_FORCE";
public final static String SIG_NEWPAGE_FORCE = "SIGNED_NEWPAGE_FORCE";
public final static String LATIN1_ENCODING = "latin1_encoding";
diff --git a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/verify/pdfbox2/PDFBOXVerifier.java b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/verify/pdfbox2/PDFBOXVerifier.java
index 270e9e28..1fab2793 100644
--- a/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/verify/pdfbox2/PDFBOXVerifier.java
+++ b/pdf-as-pdfbox-2/src/main/java/at/gv/egiz/pdfas/lib/impl/verify/pdfbox2/PDFBOXVerifier.java
@@ -33,7 +33,7 @@ public class PDFBOXVerifier implements VerifyBackend {
@Override
public List<VerifyResult> verify(VerifyParameter parameter) throws PDFASError {
int signatureToVerify = parameter.getWhichSignature();
- int currentSignature = 0;
+
PDDocument doc = null;
try {
List<VerifyResult> result = new ArrayList<VerifyResult>();
@@ -62,97 +62,136 @@ public class PDFBOXVerifier implements VerifyBackend {
return result;
}
- int lastSig = -1;
- for (int i = 0; i < fields.size(); i++) {
- COSDictionary field = (COSDictionary) fields.getObject(i);
- String type = field.getNameAsString("FT");
- if ("Sig".equals(type)) {
- lastSig = i;
- }
- }
-
+ int lastSig = selectLastSigIndex(fields);
byte[] inputData = IOUtils.toByteArray(parameter.getDataSource().getInputStream());
+ int currentSignature = 0;
for (int i = 0; i < fields.size(); i++) {
COSDictionary field = (COSDictionary) fields.getObject(i);
String type = field.getNameAsString("FT");
- if ("Sig".equals(type)) {
- boolean verifyThis = true;
-
- if (signatureToVerify >= 0) {
- // verify only specific siganture!
- verifyThis = signatureToVerify == currentSignature;
- }
-
- if (signatureToVerify == -2) {
- verifyThis = i == lastSig;
- }
-
- if (verifyThis) {
- logger.trace("Found Signature: ");
+ if ("Sig".equals(type)) {
+ if (verifyCurrentSig(signatureToVerify, i, lastSig, currentSignature)) {
+ logger.trace("Found Signature Form: ");
COSBase base = field.getDictionaryObject("V");
- COSDictionary dict = (COSDictionary) base;
-
- logger.debug("Signer: " + dict.getNameAsString("Name"));
- logger.debug("SubFilter: " + dict.getNameAsString("SubFilter"));
- logger.debug("Filter: " + dict.getNameAsString("Filter"));
- logger.debug("Modified: " + dict.getNameAsString("M"));
- COSArray byteRange = (COSArray) dict.getDictionaryObject("ByteRange");
-
- StringBuilder sb = new StringBuilder();
- int[] bytes = new int[byteRange.size()];
- for (int j = 0; j < byteRange.size(); j++) {
- bytes[j] = byteRange.getInt(j);
- sb.append(" " + bytes[j]);
- }
-
- logger.debug("ByteRange" + sb.toString());
-
- COSString content = (COSString) dict.getDictionaryObject("Contents");
-
- ByteArrayOutputStream contentData = new ByteArrayOutputStream();
- for (int j = 0; j < bytes.length; j = j + 2) {
- int offset = bytes[j];
- int length = bytes[j + 1];
-
- contentData.write(inputData, offset, length);
- }
- contentData.close();
-
- IVerifyFilter verifyFilter = verifier.getVerifier(dict.getNameAsString("Filter"),
- dict.getNameAsString("SubFilter"));
-
- IVerifier lvlVerifier = verifier.getVerifierByLevel(parameter.getSignatureVerificationLevel());
- synchronized (lvlVerifier) {
- lvlVerifier.setConfiguration(parameter.getConfiguration());
- if (verifyFilter != null) {
- List<VerifyResult> results = verifyFilter.verify(contentData.toByteArray(),
- content.getBytes(), parameter.getVerificationTime(), bytes, lvlVerifier);
- if (results != null && !results.isEmpty()) {
- result.addAll(results);
- }
- }
+ if (base != null) {
+ checkTechicalSig(base, inputData, verifier, parameter, result, i);
+
+ } else {
+ logger.info("Skipping signature form, because it looks empty");
+
}
+
}
- currentSignature++;
+
+ currentSignature++;
}
- }
+ }
return result;
+
} catch (IOException e) {
logger.warn("Failed to verify document", e);
throw ErrorExtractor.searchPdfAsError(e, null);
+
} catch (PdfAsException e) {
logger.warn("Failed to verify document", e);
throw ErrorExtractor.searchPdfAsError(e, null);
+
} finally {
if (doc != null) {
try {
doc.close();
+
} catch (IOException e) {
logger.info("Failed to close doc");
+
}
}
}
}
+ private boolean verifyCurrentSig(int signatureToVerify, int i, int lastSig, int currentSignature) {
+ if (signatureToVerify >= 0) {
+ // verify only specific siganture!
+ return signatureToVerify == currentSignature;
+
+ }
+
+ if (signatureToVerify == -2) {
+ return i == lastSig;
+
+ }
+
+ return true;
+ }
+
+ private int selectLastSigIndex(COSArray fields) {
+ int lastSig = -1;
+ for (int i = 0; i < fields.size(); i++) {
+ COSDictionary field = (COSDictionary) fields.getObject(i);
+ String type = field.getNameAsString("FT");
+ if ("Sig".equals(type)) {
+ lastSig = i;
+ }
+ }
+
+ return lastSig;
+
+ }
+
+ private void checkTechicalSig(COSBase base, byte[] inputData, VerifierDispatcher verifier, VerifyParameter parameter,
+ List<VerifyResult> result, int i) throws IOException, PdfAsException {
+ try {
+ COSDictionary dict = (COSDictionary) base;
+
+ logger.debug("Signer: " + dict.getNameAsString("Name"));
+ logger.debug("SubFilter: " + dict.getNameAsString("SubFilter"));
+ logger.debug("Filter: " + dict.getNameAsString("Filter"));
+ logger.debug("Modified: " + dict.getNameAsString("M"));
+ COSArray byteRange = (COSArray) dict.getDictionaryObject("ByteRange");
+
+ StringBuilder sb = new StringBuilder();
+ int[] bytes = new int[byteRange.size()];
+ for (int j = 0; j < byteRange.size(); j++) {
+ bytes[j] = byteRange.getInt(j);
+ sb.append(" " + bytes[j]);
+ }
+
+ logger.debug("ByteRange" + sb.toString());
+
+ COSString content = (COSString) dict.getDictionaryObject("Contents");
+
+ ByteArrayOutputStream contentData = new ByteArrayOutputStream();
+ for (int j = 0; j < bytes.length; j = j + 2) {
+ int offset = bytes[j];
+ int length = bytes[j + 1];
+
+ contentData.write(inputData, offset, length);
+ }
+ contentData.close();
+
+ IVerifyFilter verifyFilter = verifier.getVerifier(dict.getNameAsString("Filter"),
+ dict.getNameAsString("SubFilter"));
+
+ IVerifier lvlVerifier = verifier.getVerifierByLevel(parameter.getSignatureVerificationLevel());
+ synchronized (lvlVerifier) {
+ lvlVerifier.setConfiguration(parameter.getConfiguration());
+ if (verifyFilter != null) {
+ List<VerifyResult> results = verifyFilter.verify(contentData.toByteArray(),
+ content.getBytes(), parameter.getVerificationTime(), bytes, lvlVerifier);
+ if (results != null && !results.isEmpty()) {
+ result.addAll(results);
+ }
+ }
+ }
+
+ } catch (NullPointerException e) {
+ logger.info("Verification of signature #{} failed with generic error", i);
+ }
+
+ }
+
}
+
+
+
+
diff --git a/pdf-as-web/build.gradle b/pdf-as-web/build.gradle
index 067b9a96..fdf18d9e 100644
--- a/pdf-as-web/build.gradle
+++ b/pdf-as-web/build.gradle
@@ -74,6 +74,7 @@ dependencies {
api group: 'javax.jws', name: 'javax.jws-api', version: '1.1'
compileOnly 'javax.servlet:javax.servlet-api:3.0.1'
testImplementation 'org.springframework:spring-test:5.3.31'
+ testImplementation 'org.springframework:spring-web:5.3.31'
}
diff --git a/pdf-as-web/src/test/java/at/gv/egiz/pdfas/web/test/SimpleVerifyServletTest.java b/pdf-as-web/src/test/java/at/gv/egiz/pdfas/web/test/SimpleVerifyServletTest.java
new file mode 100644
index 00000000..a041f102
--- /dev/null
+++ b/pdf-as-web/src/test/java/at/gv/egiz/pdfas/web/test/SimpleVerifyServletTest.java
@@ -0,0 +1,164 @@
+package at.gv.egiz.pdfas.web.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Enumeration;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+
+import at.gv.egiz.pdfas.common.exceptions.PdfAsSettingsValidationException;
+import at.gv.egiz.pdfas.common.settings.ISettings;
+import at.gv.egiz.pdfas.lib.api.PdfAsFactory;
+import at.gv.egiz.pdfas.web.config.WebConfiguration;
+import at.gv.egiz.pdfas.web.filter.UserAgentFilter;
+import at.gv.egiz.pdfas.web.helper.PdfAsHelper;
+import at.gv.egiz.pdfas.web.servlets.VerifyServlet;
+import at.gv.egiz.pdfas.web.stats.StatisticEvent;
+import at.gv.egiz.pdfas.web.stats.StatisticEvent.Operation;
+import at.gv.egiz.pdfas.web.stats.StatisticEvent.Source;
+import lombok.SneakyThrows;
+
+//@Ignore
+@RunWith(BlockJUnit4ClassRunner.class)
+public class SimpleVerifyServletTest {
+
+
+ @BeforeClass
+ public static void classInitializer() throws IOException {
+ final String current = new java.io.File(".").getCanonicalPath();
+ System.setProperty("pdf-as-web.conf",
+ current + "/src/test/resources/config/pdfas/pdf-as-web.properties");
+
+ String webconfig = System.getProperty("pdf-as-web.conf");
+
+ if(webconfig == null) {
+ throw new RuntimeException("No web configuration provided!");
+ }
+
+ WebConfiguration.configure(webconfig);
+ PdfAsHelper.init();
+
+ try {
+ PdfAsFactory.validateConfiguration((ISettings)PdfAsHelper.getPdfAsConfig());
+
+ } catch (PdfAsSettingsValidationException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ @Test
+ @SneakyThrows
+ public void unsignedPdf() {
+ byte[] pdf = IOUtils.toByteArray(SimpleVerifyServletTest.class.getResourceAsStream("/data/enc_own.pdf"));
+ executeTest(pdf);
+
+ }
+
+ @Test
+ @SneakyThrows
+ public void unsignedWithSigField() {
+ byte[] pdf = IOUtils.toByteArray(SimpleVerifyServletTest.class.getResourceAsStream("/data/placeholder_sigfield_and_qr.pdf"));
+ executeTest(pdf);
+
+ }
+
+ @Test
+ @SneakyThrows
+ public void signedPdf() {
+ byte[] pdf = IOUtils.toByteArray(SimpleVerifyServletTest.class.getResourceAsStream("/data/dummy-pdf-signed.pdf"));
+ executeTest(pdf);
+
+ }
+
+
+
+
+ @SneakyThrows
+ private MockHttpServletResponse executeTest(byte[] pdf) {
+ MockHttpServletRequest httpReq = new MockHttpServletRequest("POST", "https://localhost/pdfas");
+ MockHttpServletResponse httpResp = new MockHttpServletResponse();
+
+ // perform operation
+ performTest(httpReq, httpResp, pdf);
+
+ //validate state
+ assertNotNull("httpResp", httpResp);
+ assertEquals("httpStatus", 200, httpResp.getStatus());
+ String body = httpResp.getContentAsString();
+ assertFalse("Empty body", StringUtils.isEmpty(body));
+
+ return httpResp;
+
+ }
+
+
+ private void performTest(HttpServletRequest httpReq, HttpServletResponse httpResp, byte[] pdf) throws NoSuchMethodException,
+ SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, ServletException {
+ VerifyServlet servlet = new VerifyServlet ();
+ ServletConfig servletConfig = buildServletConfig();
+ servlet.init(servletConfig);
+
+ Method method = servlet.getClass().getDeclaredMethod("doVerify",
+ HttpServletRequest.class, HttpServletResponse.class, byte[].class, StatisticEvent.class);
+ method.setAccessible(true);
+ StatisticEvent statisticEvent = new StatisticEvent();
+ statisticEvent.setStartNow();
+ statisticEvent.setSource(Source.WEB);
+ statisticEvent.setOperation(Operation.VERIFY);
+ statisticEvent.setUserAgent(UserAgentFilter.getUserAgent());
+
+ method.invoke(servlet, httpReq, httpResp, pdf, statisticEvent);
+
+ }
+
+
+ private ServletConfig buildServletConfig() {
+
+ return new ServletConfig() {
+
+ private ServletContext servletContext = null;
+
+ @Override
+ public String getServletName() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public ServletContext getServletContext() {
+ return servletContext;
+ }
+
+ @Override
+ public Enumeration<String> getInitParameterNames() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public String getInitParameter(String name) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+ };
+ }
+}
diff --git a/pdf-as-web/src/test/resources/data/dummy-pdf-signed.pdf b/pdf-as-web/src/test/resources/data/dummy-pdf-signed.pdf
new file mode 100644
index 00000000..e66d621e
--- /dev/null
+++ b/pdf-as-web/src/test/resources/data/dummy-pdf-signed.pdf
Binary files differ
diff --git a/pdf-as-web/src/test/resources/data/placeholder_sigfield_and_qr.pdf b/pdf-as-web/src/test/resources/data/placeholder_sigfield_and_qr.pdf
new file mode 100644
index 00000000..e7a29895
--- /dev/null
+++ b/pdf-as-web/src/test/resources/data/placeholder_sigfield_and_qr.pdf
Binary files differ