diff --git a/.mvn/wrapper/dists/apache-maven-3.6.3-bin.zip b/.mvn/wrapper/dists/apache-maven-3.8.2-bin.zip
similarity index 67%
rename from .mvn/wrapper/dists/apache-maven-3.6.3-bin.zip
rename to .mvn/wrapper/dists/apache-maven-3.8.2-bin.zip
index c6b789fde..04932490d 100644
Binary files a/.mvn/wrapper/dists/apache-maven-3.6.3-bin.zip and b/.mvn/wrapper/dists/apache-maven-3.8.2-bin.zip differ
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index 72853742a..4a7b6433d 100644
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -1 +1 @@
-distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
\ No newline at end of file
+distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.8.2/apache-maven-3.8.2-bin.zip
\ No newline at end of file
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index b620d0777..ae68621cc 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -1,6 +1,25 @@
DigiDoc4J Java library release notes
------------------------------------
+Release 4.2.1
+------------------
+Summary of the major changes since 4.2.0
+------------------------------------------
+* ZIP-bombing detection improvements and configurability
+* Upgrade of TSL TLS truststore
+* Dependencies update
+
+Known issues
+------------
+* We have noticed a decrease in performance with the introduction of properly accessing AIA certificate resources
+* Opening a container that contains signatures, triggers TSL loading (TSL lazy loading does not work as expected)
+* While upgrading from versions older than 2.1.1 be sure that your integration :
+ - doesn't use Xalan or XercesImpl dependencies
+ - uses a patched Java version (JDK8 or higher)
+ Xalan and XercesImpl were used to patch XML vulnerabilities in older java versions. They should be discarded with higher versions because they override default Java XML security.
+ If it is not possible to remove Xalan, then you can set your system property to override TransformerFactory : System.setProperty("javax.xml.transform.TransformerFactory","com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
+
+
Release 4.2.0
------------------
Summary of the major changes since 4.1.1
diff --git a/ddoc4j/pom.xml b/ddoc4j/pom.xml
index 933877488..0aa2cc2c9 100644
--- a/ddoc4j/pom.xml
+++ b/ddoc4j/pom.xml
@@ -6,7 +6,7 @@
org.digidoc4j
ddoc4j
jar
- 4.2.0
+ 4.2.1
DDoc4J
DDoc4J is Java Library for validating DDOC documents. It's not recommended to use it directly but rather through DigiDoc4J's API.
@@ -15,7 +15,7 @@
digidoc4j-parent
org.digidoc4j
- 4.2.0
+ 4.2.1
diff --git a/digidoc4j/pom.xml b/digidoc4j/pom.xml
index 577f8a39f..a0b61e8c7 100644
--- a/digidoc4j/pom.xml
+++ b/digidoc4j/pom.xml
@@ -7,7 +7,7 @@
org.digidoc4j
digidoc4j
jar
- 4.2.0
+ 4.2.1
DigiDoc4j
DigiDoc4j is a Java library for digitally signing documents and creating digital signature containers
@@ -18,13 +18,13 @@
digidoc4j-parent
org.digidoc4j
- 4.2.0
+ 4.2.1
2.2
1.2.3
- 2.12.3
+ 2.12.4
4.13.2
org.digidoc4j.dss
5.7.d4j.2
@@ -45,7 +45,7 @@
ddoc4j
org.digidoc4j
- 4.2.0
+ 4.2.1
@@ -69,7 +69,7 @@
commons-io
commons-io
- 2.8.0
+ 2.11.0
org.apache.commons
@@ -105,7 +105,7 @@
org.yaml
snakeyaml
- 1.28
+ 1.29
org.slf4j
@@ -212,7 +212,7 @@
org.apache.pdfbox
pdfbox
- 2.0.23
+ 2.0.24
${dss.groupId}
@@ -243,7 +243,7 @@
org.glassfish.jaxb
jaxb-runtime
- 2.3.4
+ 2.3.5
javax.activation
@@ -253,7 +253,7 @@
com.sun.xml.bind
jaxb-impl
- 2.3.4
+ 2.3.5
com.sun.xml.bind
@@ -287,7 +287,7 @@
org.mockito
mockito-core
- 3.10.0
+ 3.12.1
test
diff --git a/digidoc4j/src/main/java/org/digidoc4j/Configuration.java b/digidoc4j/src/main/java/org/digidoc4j/Configuration.java
index 1518a4372..125aae044 100644
--- a/digidoc4j/src/main/java/org/digidoc4j/Configuration.java
+++ b/digidoc4j/src/main/java/org/digidoc4j/Configuration.java
@@ -24,11 +24,28 @@
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.Yaml;
-import java.io.*;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
import java.util.concurrent.ExecutorService;
+import java.util.function.BiPredicate;
import static java.util.Arrays.asList;
@@ -143,6 +160,10 @@
* NB! Strict Validation applied by default.
* ALLOWED_OCSP_RESPONDERS_FOR_TM: whitelist of OCSP responders for timemark validation
* (for example: SK OCSP RESPONDER 2011, ESTEID-SK OCSP RESPONDER, KLASS3-SK OCSP RESPONDER)
+ * ZIP_COMPRESSION_RATIO_CHECK_THRESHOLD_IN_BYTES: the threshold of how much memory (in bytes) are the unpacked
+ * contents of a ZIP-based container allowed to consume before ZIP compression ratio check kicks in
+ * MAX_ALLOWED_ZIP_COMPRESSION_RATIO: the maximum ratio of how much are the contents of a ZIP-based container
+ * allowed to expand on unpacking before the container is considered harmful.
*
*/
public class Configuration implements Serializable {
@@ -1670,6 +1691,63 @@ public List getAllowedOcspRespondersForTM() {
return this.getConfigurationValues(ConfigurationParameter.AllowedOcspRespondersForTM);
}
+ /**
+ * Set the maximum ratio of how much are the contents of a ZIP-based container allowed to expand on unpacking before
+ * the container is considered harmful.
+ *
+ * @see #setZipCompressionRatioCheckThresholdInBytes(long)
+ * @see #getZipCompressionRatioCheckThresholdInBytes()
+ *
+ * @param maxAllowedZipCompressionRatio maximum ratio of how much are the contents of a ZIP-based container allowed to expand
+ */
+ public void setMaxAllowedZipCompressionRatio(int maxAllowedZipCompressionRatio) {
+ setConfigurationParameter(ConfigurationParameter.MaxAllowedZipCompressionRatio, String.valueOf(maxAllowedZipCompressionRatio));
+ }
+
+ /**
+ * Get the maximum ratio of how much are the contents of a ZIP-based container allowed to expand on unpacking before
+ * the container is considered harmful.
+ *
+ * @see #setZipCompressionRatioCheckThresholdInBytes(long)
+ * @see #getZipCompressionRatioCheckThresholdInBytes()
+ *
+ * @return maximum ratio of how much are the contents of a ZIP-based container allowed to expand
+ */
+ public int getMaxAllowedZipCompressionRatio() {
+ return Optional
+ .ofNullable(getConfigurationParameter(ConfigurationParameter.MaxAllowedZipCompressionRatio, Integer.class))
+ .orElse(Integer.MAX_VALUE);
+ }
+
+ /**
+ * Set the threshold of how much memory (in bytes) are the unpacked contents of a ZIP-based container allowed to
+ * consume before ZIP compression ratio check kicks in.
+ *
+ * @see #setMaxAllowedZipCompressionRatio(int)
+ * @see #getMaxAllowedZipCompressionRatio()
+ *
+ * @param zipCompressionRatioCheckThresholdInBytes threshold of how much memory are the unpacked contents of
+ * a ZIP-based container allowed to consume
+ */
+ public void setZipCompressionRatioCheckThresholdInBytes(long zipCompressionRatioCheckThresholdInBytes) {
+ setConfigurationParameter(ConfigurationParameter.ZipCompressionRatioCheckThreshold, String.valueOf(zipCompressionRatioCheckThresholdInBytes));
+ }
+
+ /**
+ * Get the threshold of how much memory (in bytes) are the unpacked contents of a ZIP-based container allowed to
+ * consume before ZIP compression ratio check kicks in.
+ *
+ * @see #setMaxAllowedZipCompressionRatio(int)
+ * @see #getMaxAllowedZipCompressionRatio()
+ *
+ * @return threshold of how much memory are the unpacked contents of a ZIP-based container allowed to consume
+ */
+ public long getZipCompressionRatioCheckThresholdInBytes() {
+ return Optional
+ .ofNullable(getConfigurationParameter(ConfigurationParameter.ZipCompressionRatioCheckThreshold, Long.class))
+ .orElse(Long.MAX_VALUE);
+ }
+
/**
* @return true when configuration is Configuration.Mode.TEST
* @see Configuration.Mode#TEST
@@ -1738,6 +1816,8 @@ private void initDefaultValues() {
this.setConfigurationParameter(ConfigurationParameter.IsFullSimpleReportNeeded,
Constant.Default.FULL_SIMPLE_REPORT);
this.setConfigurationParameter(ConfigurationParameter.useNonce, "true");
+ this.setConfigurationParameter(ConfigurationParameter.ZipCompressionRatioCheckThreshold, "1048576");
+ this.setConfigurationParameter(ConfigurationParameter.MaxAllowedZipCompressionRatio, "100");
if (Mode.TEST.equals(this.mode)) {
this.setConfigurationParameter(ConfigurationParameter.TspSource, Constant.Test.TSP_SOURCE);
this.setConfigurationParameter(ConfigurationParameter.TslLocation, Constant.Test.TSL_LOCATION);
@@ -1849,6 +1929,10 @@ private void loadInitialConfigurationValues() {
this.setConfigurationParameter(ConfigurationParameter.AllowASN1UnsafeInteger, this.getParameter(Constant
.System.ORG_BOUNCYCASTLE_ASN1_ALLOW_UNSAFE_INTEGER, "ALLOW_UNSAFE_INTEGER"));
this.setConfigurationParameter(ConfigurationParameter.preferAiaOcsp, this.getParameterFromFile("PREFER_AIA_OCSP"));
+ this.setConfigurationParameterFromFile("ZIP_COMPRESSION_RATIO_CHECK_THRESHOLD_IN_BYTES",
+ ConfigurationParameter.ZipCompressionRatioCheckThreshold, this::isValidLongParameter);
+ this.setConfigurationParameterFromFile("MAX_ALLOWED_ZIP_COMPRESSION_RATIO",
+ ConfigurationParameter.MaxAllowedZipCompressionRatio, this::isValidIntegerParameter);
this.loadYamlOcspResponders();
this.loadYamlTrustedTerritories();
this.loadYamlTSPs();
@@ -1955,7 +2039,7 @@ private boolean isValidBooleanParameter(String configParameter, String value) {
}
private boolean isValidIntegerParameter(String configParameter, String value) {
- Integer parameterValue;
+ int parameterValue;
try {
parameterValue = Integer.parseInt(value);
} catch (Exception e) {
@@ -1973,6 +2057,18 @@ private boolean isValidIntegerParameter(String configParameter, String value) {
return true;
}
+ private boolean isValidLongParameter(String configParameter, String value) {
+ try {
+ Long.parseLong(value);
+ } catch (Exception e) {
+ String errorMessage = "Configuration parameter " + configParameter + " should have a long integer value"
+ + " but the actual value is: " + value + ".";
+ this.logError(errorMessage);
+ return false;
+ }
+ return true;
+ }
+
private void loadOCSPCertificates(LinkedHashMap digiDocCA, String caPrefix) {
String errorMessage;
@SuppressWarnings("unchecked")
@@ -2162,13 +2258,17 @@ private List getStringListParameterFromFile(String key) {
return Arrays.asList(value.split("\\s*,\\s*")); //Split by comma and trim whitespace
}
- private void setConfigurationParameterFromFile(String fileKey, ConfigurationParameter parameter) {
+ private void setConfigurationParameterFromFile(String fileKey, ConfigurationParameter parameter, BiPredicate parameterValidator) {
String fileValue = this.getParameterFromFile(fileKey);
- if (fileValue != null) {
- this.setConfigurationParameter(parameter, fileValue.toString());
+ if (fileValue != null && (parameterValidator == null || parameterValidator.test(fileKey, fileValue))) {
+ this.setConfigurationParameter(parameter, fileValue);
}
}
+ private void setConfigurationParameterFromFile(String fileKey, ConfigurationParameter parameter) {
+ setConfigurationParameterFromFile(fileKey, parameter, null);
+ }
+
private void setConfigurationParameterFromFile(ConfigurationParameter parameter) {
setConfigurationParameterFromFile(parameter.fileKey, parameter);
}
diff --git a/digidoc4j/src/main/java/org/digidoc4j/ConfigurationParameter.java b/digidoc4j/src/main/java/org/digidoc4j/ConfigurationParameter.java
index 44b60f816..3d744abad 100644
--- a/digidoc4j/src/main/java/org/digidoc4j/ConfigurationParameter.java
+++ b/digidoc4j/src/main/java/org/digidoc4j/ConfigurationParameter.java
@@ -98,7 +98,10 @@ public enum ConfigurationParameter {
TspSslProtocol("TSP_SSL_PROTOCOL"),
TspSupportedSslProtocols("TSP_SUPPORTED_SSL_PROTOCOLS"),
TspSupportedSslCipherSuites("TSP_SUPPORTED_SSL_CIPHER_SUITES"),
- TempFileMaxAgeInMillis;
+
+ TempFileMaxAgeInMillis,
+ MaxAllowedZipCompressionRatio,
+ ZipCompressionRatioCheckThreshold;
final String fileKey;
diff --git a/digidoc4j/src/main/java/org/digidoc4j/impl/asic/AsicContainerParser.java b/digidoc4j/src/main/java/org/digidoc4j/impl/asic/AsicContainerParser.java
index 93a7b106f..198f04fd8 100644
--- a/digidoc4j/src/main/java/org/digidoc4j/impl/asic/AsicContainerParser.java
+++ b/digidoc4j/src/main/java/org/digidoc4j/impl/asic/AsicContainerParser.java
@@ -11,7 +11,6 @@
package org.digidoc4j.impl.asic;
import eu.europa.esig.dss.model.DSSDocument;
-import eu.europa.esig.dss.model.DSSException;
import eu.europa.esig.dss.model.InMemoryDocument;
import eu.europa.esig.dss.model.MimeType;
import org.apache.commons.io.IOUtils;
@@ -53,37 +52,38 @@ public abstract class AsicContainerParser {
public static final String MANIFEST = "META-INF/manifest.xml";
public static final String TIMESTAMP_TOKEN = "META-INF/timestamp.tst";
+
private static final Logger logger = LoggerFactory.getLogger(AsicContainerParser.class);
//Matches META-INF/*signatures*.xml where the last * is a number
private static final String SIGNATURES_FILE_REGEX = "META-INF/(.*)signatures(.*).xml";
private static final Pattern SIGNATURE_FILE_ENDING_PATTERN = Pattern.compile("(\\d+).xml");
+
private final Configuration configuration;
- private AsicParseResult parseResult = new AsicParseResult();
- private List signatures = new ArrayList<>();
- private LinkedHashMap dataFiles = new LinkedHashMap<>();
- private List detachedContents = new ArrayList<>();
+ private final AsicParseResult parseResult = new AsicParseResult();
+ private final List signatures = new ArrayList<>();
+ private final LinkedHashMap dataFiles = new LinkedHashMap<>();
+ private final List detachedContents = new ArrayList<>();
private Integer currentSignatureFileIndex;
private String mimeType;
private String zipFileComment;
- private List asicEntries = new ArrayList<>();
+ private final List asicEntries = new ArrayList<>();
private Map manifestFileItems = Collections.emptyMap();
private ManifestParser manifestParser;
- private boolean storeDataFilesOnlyInMemory;
+ private final boolean storeDataFilesOnlyInMemory;
private boolean manifestFound = false;
private boolean mimeTypeFound = false;
- private long maxDataFileCachedInBytes;
+ private final long maxDataFileCachedInBytes;
private DataFile timestampToken;
- protected static final long ZIP_ENTRY_THRESHOLD = 1000000; // 1 MB
- /**
- * Maximum compression ratio.
- */
- protected static final long ZIP_ENTRY_RATIO = 50;
+ private final long zipCompressionRatioCheckThreshold;
+ private final long zipMaxAllowedCompressionRatio;
protected AsicContainerParser(Configuration configuration) {
this.configuration = configuration;
storeDataFilesOnlyInMemory = configuration.storeDataFilesOnlyInMemory();
maxDataFileCachedInBytes = configuration.getMaxDataFileCachedInBytes();
+ zipCompressionRatioCheckThreshold = configuration.getZipCompressionRatioCheckThresholdInBytes();
+ zipMaxAllowedCompressionRatio = configuration.getMaxAllowedZipCompressionRatio();
}
/**
@@ -138,25 +138,30 @@ protected void parseEntry(ZipEntry entry) {
}
private void extractMimeType(ZipEntry entry) {
- try {
- InputStream zipFileInputStream = getZipEntryInputStream(entry);
- BOMInputStream bomInputStream = new BOMInputStream(zipFileInputStream);
- InMemoryDocument document = new InMemoryDocument(bomInputStream);
+ try (
+ InputStream zipFileInputStream = getZipEntryInputStream(entry);
+ BOMInputStream bomInputStream = new BOMInputStream(zipFileInputStream)
+ ) {
+ InMemoryDocument document = new InMemoryDocument(IOUtils.toByteArray(bomInputStream));
mimeType = StringUtils.trim(IOUtils.toString(document.getBytes(), "UTF-8"));
extractUncompressedAsicEntry(entry, document);
} catch (IOException e) {
logger.error("Error parsing container mime type: " + e.getMessage());
- throw new TechnicalException("Error parsing container mime type: " + e.getMessage(), e);
+ throw new TechnicalException("Error parsing container mime type", e);
}
}
private void extractSignature(ZipEntry entry) {
logger.debug("Extracting signature");
- InputStream zipFileInputStream = getZipEntryInputStream(entry);
- String fileName = entry.getName();
- InMemoryDocument document = new InMemoryDocument(zipFileInputStream, fileName);
- signatures.add(document);
- extractSignatureAsicEntry(entry, document);
+ try (InputStream zipFileInputStream = getZipEntryInputStream(entry)) {
+ String fileName = entry.getName();
+ InMemoryDocument document = new InMemoryDocument(IOUtils.toByteArray(zipFileInputStream), fileName);
+ signatures.add(document);
+ extractSignatureAsicEntry(entry, document);
+ } catch (IOException e) {
+ logger.error("Error parsing container signature: " + e.getMessage());
+ throw new TechnicalException("Error parsing container signature", e);
+ }
}
private void extractTimeStamp(ZipEntry entry) {
@@ -307,6 +312,15 @@ private void determineCurrentSignatureFileIndex(String entryName) {
}
}
+ protected void verifyContainerUnpackingIsSafeToProceed(long packedSize, long unpackedSize) {
+ if (unpackedSize > zipCompressionRatioCheckThreshold) {
+ final long allowedMaximumUnpackedSize = packedSize * zipMaxAllowedCompressionRatio;
+ if (unpackedSize > allowedMaximumUnpackedSize) {
+ throw new TechnicalException("Zip Bomb detected in the ZIP container. Validation is interrupted.");
+ }
+ }
+ }
+
void setZipFileComment(String zipFileComment) {
this.zipFileComment = zipFileComment;
}
diff --git a/digidoc4j/src/main/java/org/digidoc4j/impl/asic/AsicFileContainerParser.java b/digidoc4j/src/main/java/org/digidoc4j/impl/asic/AsicFileContainerParser.java
index 4caa8a321..ed568f910 100644
--- a/digidoc4j/src/main/java/org/digidoc4j/impl/asic/AsicFileContainerParser.java
+++ b/digidoc4j/src/main/java/org/digidoc4j/impl/asic/AsicFileContainerParser.java
@@ -31,8 +31,11 @@
public class AsicFileContainerParser extends AsicContainerParser {
private static final Logger logger = LoggerFactory.getLogger(AsicFileContainerParser.class);
- private ZipFile zipFile;
- private long containerSize;
+ private static final int READ_BUFFER_SIZE = 2048;
+
+ private final ZipFile zipFile;
+ private final long containerSize;
+ private long totalContainerBytesUnpacked;
/**
* @param containerPath path
@@ -57,27 +60,27 @@ protected void parseContainer() {
setZipFileComment(zipFileComment);
parseZipFileManifest();
Enumeration extends ZipEntry> entries = zipFile.entries();
+ totalContainerBytesUnpacked = 0L;
while (entries.hasMoreElements()) {
ZipEntry zipEntry = entries.nextElement();
- verifyIfZipBomb(getZipEntryInputStream(zipEntry));
+ verifyIfZipBomb(zipEntry);
parseEntry(zipEntry);
}
} catch (IOException e) {
- throw new TechnicalException("Zip Bomb detected in the ZIP container. Validation is interrupted.");
+ logger.error("Error reading asic container file: " + e.getMessage());
+ throw new TechnicalException("Error reading asic container file", e);
} finally {
IOUtils.closeQuietly(zipFile);
}
}
- private void verifyIfZipBomb(InputStream inputStream) throws IOException {
- byte[] data = new byte[2048];
- int nRead;
- int byteCounter = 0;
- long allowedSize = containerSize * ZIP_ENTRY_RATIO;
- while ((nRead = inputStream.read(data)) != -1) {
- byteCounter += nRead;
- if (byteCounter > ZIP_ENTRY_THRESHOLD && byteCounter > allowedSize) {
- throw new TechnicalException("Zip Bomb detected in the ZIP container. Validation is interrupted.");
+ private void verifyIfZipBomb(ZipEntry zipEntry) throws IOException {
+ try (InputStream inputStream = getZipEntryInputStream(zipEntry)) {
+ byte[] readBuffer = new byte[READ_BUFFER_SIZE];
+ int bytesRead;
+ while ((bytesRead = inputStream.read(readBuffer)) != -1) {
+ totalContainerBytesUnpacked += bytesRead;
+ verifyContainerUnpackingIsSafeToProceed(containerSize, totalContainerBytesUnpacked);
}
}
}
diff --git a/digidoc4j/src/main/java/org/digidoc4j/impl/asic/AsicStreamContainerParser.java b/digidoc4j/src/main/java/org/digidoc4j/impl/asic/AsicStreamContainerParser.java
index 36550d159..5ef54399e 100644
--- a/digidoc4j/src/main/java/org/digidoc4j/impl/asic/AsicStreamContainerParser.java
+++ b/digidoc4j/src/main/java/org/digidoc4j/impl/asic/AsicStreamContainerParser.java
@@ -31,10 +31,10 @@
public class AsicStreamContainerParser extends AsicContainerParser {
private static final Logger logger = LoggerFactory.getLogger(AsicStreamContainerParser.class);
- private ZipInputStream zipInputStream;
- private long entryOffset;
- private static final int BUFFER_SIZE = 512;
- CountingInputStream countingInputStream;
+
+ private final CountingInputStream countingInputStream;
+ private final ZipInputStream zipInputStream;
+ private long totalContainerBytesUnpacked;
/**
* @param inputStream input stream
@@ -42,8 +42,8 @@ public class AsicStreamContainerParser extends AsicContainerParser {
*/
public AsicStreamContainerParser(InputStream inputStream, Configuration configuration) {
super(configuration);
- this.countingInputStream = new CountingInputStream(inputStream);
- zipInputStream = new ZipInputStream(this.countingInputStream);
+ countingInputStream = new CountingInputStream(inputStream);
+ zipInputStream = new ZipInputStream(countingInputStream);
}
@Override
@@ -56,14 +56,13 @@ private void parseZipStream() {
logger.debug("Parsing zip stream");
try {
ZipEntry entry;
-
+ totalContainerBytesUnpacked = 0L;
while ((entry = zipInputStream.getNextEntry()) != null) {
- this.entryOffset = this.countingInputStream.getByteCount();
parseEntry(entry);
}
} catch (IOException e) {
logger.error("Error reading asic container stream: " + e.getMessage());
- throw new TechnicalException("Error reading asic container stream: ", e);
+ throw new TechnicalException("Error reading asic container stream", e);
} finally {
IOUtils.closeQuietly(zipInputStream);
}
@@ -85,19 +84,13 @@ protected void extractManifest(ZipEntry entry) {
@Override
protected InputStream getZipEntryInputStream(ZipEntry entry) {
- return new ZipEntryInputStream(zipInputStream, new EntryValidator()::validate);
+ return new ZipEntryInputStream(zipInputStream, this::validate);
}
- protected class EntryValidator {
- public void validate(long unCompressed) throws IOException {
- long compressed = countingInputStream.getByteCount() - entryOffset;
- if (compressed < BUFFER_SIZE) {
- compressed = BUFFER_SIZE;
- }
- if (unCompressed > ZIP_ENTRY_THRESHOLD && compressed * ZIP_ENTRY_RATIO < unCompressed) {
- throw new IOException("Zip Bomb detected in the ZIP container. Validation is interrupted.");
- }
- }
+ private void validate(long bytesRead) {
+ long totalCompressedBytesRead = countingInputStream.getByteCount();
+ totalContainerBytesUnpacked += bytesRead;
+ verifyContainerUnpackingIsSafeToProceed(totalCompressedBytesRead, totalContainerBytesUnpacked);
}
}
diff --git a/digidoc4j/src/main/java/org/digidoc4j/main/DigiDoc4J.java b/digidoc4j/src/main/java/org/digidoc4j/main/DigiDoc4J.java
index 1fd0cf73a..77e4dd0aa 100644
--- a/digidoc4j/src/main/java/org/digidoc4j/main/DigiDoc4J.java
+++ b/digidoc4j/src/main/java/org/digidoc4j/main/DigiDoc4J.java
@@ -324,8 +324,8 @@ private static Option outputDir() {
private static Option type() {
return OptionBuilder.withArgName("type").hasArg()
- .withDescription("sets container type. Types can be DDOC, BDOC, ASICE or ASICS").withLongOpt("type").create(
- "t");
+ .withDescription("sets container type. Types can be BDOC, ASICE or ASICS")
+ .withLongOpt("type").create("t");
}
private static Option extractDataFile() {
diff --git a/digidoc4j/src/main/java/org/digidoc4j/utils/ZipEntryInputStream.java b/digidoc4j/src/main/java/org/digidoc4j/utils/ZipEntryInputStream.java
index 619b4e202..7c49cf171 100644
--- a/digidoc4j/src/main/java/org/digidoc4j/utils/ZipEntryInputStream.java
+++ b/digidoc4j/src/main/java/org/digidoc4j/utils/ZipEntryInputStream.java
@@ -2,6 +2,7 @@
import java.io.IOException;
import java.io.InputStream;
+import java.util.function.LongConsumer;
import java.util.zip.ZipInputStream;
/**
@@ -11,12 +12,11 @@
public class ZipEntryInputStream extends InputStream {
private final ZipInputStream zipInputStream;
- private final ThrowingConsumer validator;
- private int counter;
+ private final LongConsumer inputReadListener;
- public ZipEntryInputStream(ZipInputStream zipInputStream, ThrowingConsumer validator) {
+ public ZipEntryInputStream(ZipInputStream zipInputStream, LongConsumer inputReadListener) {
this.zipInputStream = zipInputStream;
- this.validator = validator;
+ this.inputReadListener = inputReadListener;
}
@Override
@@ -41,23 +41,23 @@ public boolean markSupported() {
@Override
public int read() throws IOException {
- int result = zipInputStream.read();
- checkEntry(result);
- return result;
+ int valueRead = zipInputStream.read();
+ notifyInputRead(valueRead < 0 ? 0L : 1L);
+ return valueRead;
}
@Override
public int read(byte[] b) throws IOException {
- int result = zipInputStream.read(b);
- checkEntry(result);
- return result;
+ int bytesRead = zipInputStream.read(b);
+ notifyInputRead(bytesRead < 0 ? 0L : bytesRead);
+ return bytesRead;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
- int result = zipInputStream.read(b, off, len);
- checkEntry(result);
- return result;
+ int bytesRead = zipInputStream.read(b, off, len);
+ notifyInputRead(bytesRead < 0 ? 0L : bytesRead);
+ return bytesRead;
}
@Override
@@ -67,19 +67,15 @@ public void reset() throws IOException {
@Override
public long skip(long n) throws IOException {
- return zipInputStream.skip(n);
+ long bytesSkipped = zipInputStream.skip(n);
+ notifyInputRead(bytesSkipped);
+ return bytesSkipped;
}
- private void checkEntry(int result) throws IOException {
- if (validator != null) {
- counter += result;
- validator.accept(counter);
+ private void notifyInputRead(long bytesRead) {
+ if (inputReadListener != null) {
+ inputReadListener.accept(bytesRead);
}
}
- @FunctionalInterface
- public interface ThrowingConsumer {
- void accept(T t) throws E;
- }
-
}
diff --git a/digidoc4j/src/main/resources/keystore/keystore.jks b/digidoc4j/src/main/resources/keystore/keystore.jks
index e954ebbc9..d0382257a 100644
Binary files a/digidoc4j/src/main/resources/keystore/keystore.jks and b/digidoc4j/src/main/resources/keystore/keystore.jks differ
diff --git a/digidoc4j/src/main/resources/keystore/test-keystore.jks b/digidoc4j/src/main/resources/keystore/test-keystore.jks
index 898f1e42a..752ebf252 100644
Binary files a/digidoc4j/src/main/resources/keystore/test-keystore.jks and b/digidoc4j/src/main/resources/keystore/test-keystore.jks differ
diff --git a/digidoc4j/src/main/resources/ssl/tsl_truststore.p12 b/digidoc4j/src/main/resources/ssl/tsl_truststore.p12
index 57df1d700..d1d3f09e0 100644
Binary files a/digidoc4j/src/main/resources/ssl/tsl_truststore.p12 and b/digidoc4j/src/main/resources/ssl/tsl_truststore.p12 differ
diff --git a/digidoc4j/src/test/java/org/digidoc4j/AbstractTest.java b/digidoc4j/src/test/java/org/digidoc4j/AbstractTest.java
index e3053cd2a..a24ee1604 100644
--- a/digidoc4j/src/test/java/org/digidoc4j/AbstractTest.java
+++ b/digidoc4j/src/test/java/org/digidoc4j/AbstractTest.java
@@ -57,6 +57,7 @@
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import static org.digidoc4j.Container.DocumentType.ASICE;
import static org.digidoc4j.Container.DocumentType.ASICS;
@@ -83,7 +84,7 @@ public abstract class AbstractTest extends ConfigurationSingeltonHolder {
protected static final String USER_AGENT_STRING = "test-user-agent";
protected static final PKCS12SignatureToken pkcs12SignatureToken = new PKCS12SignatureToken("src/test/resources/testFiles/p12/sign_RSA_from_TEST_of_ESTEIDSK2015.p12", "1234".toCharArray());
- protected static final PKCS12SignatureToken pkcs12EccSignatureToken = new PKCS12SignatureToken("src/test/resources/testFiles/p12/MadDogOY.p12", "test".toCharArray());
+ protected static final PKCS12SignatureToken pkcs12EccSignatureToken = new PKCS12SignatureToken("src/test/resources/testFiles/p12/sign_ECC_from_TEST_of_ESTEIDSK2015.p12", "1234".toCharArray());
protected static final PKCS12SignatureToken pkcs12Esteid2018SignatureToken = new PKCS12SignatureToken("src/test/resources/testFiles/p12/sign_ESTEID2018.p12", "1234".toCharArray());
protected Configuration configuration;
@@ -481,12 +482,12 @@ protected static T assertThrows(Class type, Potentially
toTest.run();
} catch (Throwable t) {
if (type.isInstance(t)) {
- return (T) t;
+ return Objects.requireNonNull((T) t, "Caught exception cannot be null");
}
Assert.fail(String.format("Expected %s, but an %s was thrown: %s", type.getSimpleName(), t.getClass().getSimpleName(), t.getMessage()));
}
Assert.fail(String.format("Expected %s, but nothing was thrown", type.getSimpleName()));
- return null; // For compiler
+ throw new IllegalStateException("Should have not reached here!"); // For compiler
}
protected void assertBDocContainer(Container container) {
diff --git a/digidoc4j/src/test/java/org/digidoc4j/AiaOcspTest.java b/digidoc4j/src/test/java/org/digidoc4j/AiaOcspTest.java
index c68199652..19bef813b 100644
--- a/digidoc4j/src/test/java/org/digidoc4j/AiaOcspTest.java
+++ b/digidoc4j/src/test/java/org/digidoc4j/AiaOcspTest.java
@@ -64,7 +64,7 @@ public void signAsiceContainerWithEccTokenUsingAiaOcsp() {
.build();
this.createSignatureBy(container, this.pkcs12EccSignatureToken);
assertTrue(container.validate().isValid());
- assertEquals("C=EE, O=SK ID Solutions AS, OU=OCSP, CN=DEMO of ESTEID-SK 2011 AIA OCSP RESPONDER 2018", container.getSignatures().get(0).getOCSPCertificate().getSubjectName());
+ assertEquals("C=EE, O=SK ID Solutions AS, OU=OCSP, CN=DEMO of ESTEID-SK 2015 AIA OCSP RESPONDER 2018", container.getSignatures().get(0).getOCSPCertificate().getSubjectName());
}
@Test
diff --git a/digidoc4j/src/test/java/org/digidoc4j/ConfigurationTest.java b/digidoc4j/src/test/java/org/digidoc4j/ConfigurationTest.java
index 960cc78f6..c14b3f354 100644
--- a/digidoc4j/src/test/java/org/digidoc4j/ConfigurationTest.java
+++ b/digidoc4j/src/test/java/org/digidoc4j/ConfigurationTest.java
@@ -1044,6 +1044,38 @@ public void getSslConfigurationFromConfigurationFile_specificParametersSet() thr
}
}
+ @Test
+ public void testDefaultZipCompressionConfiguration() {
+ Assert.assertEquals(1024 * 1024, this.configuration.getZipCompressionRatioCheckThresholdInBytes());
+ Assert.assertEquals(100, this.configuration.getMaxAllowedZipCompressionRatio());
+ }
+
+ @Test
+ public void getInvalidZipCompressionRatioCheckThresholdInBytes() {
+ this.expectedException.expect(ConfigurationException.class);
+ this.expectedException.expectMessage("Configuration parameter ZIP_COMPRESSION_RATIO_CHECK_THRESHOLD_IN_BYTES should have a long integer value but the actual value is: invalidValue.");
+ this.configuration.loadConfiguration("src/test/resources/testFiles/yaml-configurations/digidoc_test_conf_invalid_zip_threshold.yaml");
+ }
+
+ @Test
+ public void getInvalidMaxAllowedZipCompressionRatio() {
+ this.expectedException.expect(ConfigurationException.class);
+ this.expectedException.expectMessage("Configuration parameter MAX_ALLOWED_ZIP_COMPRESSION_RATIO should have an integer value but the actual value is: invalidValue.");
+ this.configuration.loadConfiguration("src/test/resources/testFiles/yaml-configurations/digidoc_test_conf_invalid_zip_ratio.yaml");
+ }
+
+ @Test
+ public void setZipCompressionRatioCheckThresholdInBytes() {
+ this.configuration.setZipCompressionRatioCheckThresholdInBytes(1234567);
+ Assert.assertEquals(1234567, this.configuration.getZipCompressionRatioCheckThresholdInBytes());
+ }
+
+ @Test
+ public void setMaxAllowedZipCompressionRatio() {
+ this.configuration.setMaxAllowedZipCompressionRatio(2345);
+ Assert.assertEquals(2345, this.configuration.getMaxAllowedZipCompressionRatio());
+ }
+
@Test
public void loadMultipleCAsFromConfigurationFile() throws Exception {
Hashtable ddoc4jConf = this.configuration.loadConfiguration("src/test/resources/testFiles/yaml-configurations/digidoc_test_conf_two_cas.yaml");
@@ -1122,6 +1154,8 @@ public void verifyAllOptionalConfigurationSettingsAreLoadedFromFile() throws Exc
Assert.assertEquals("TEST_VALIDATION_POLICY", this.configuration.getRegistry().get(ConfigurationParameter.ValidationPolicy).get(0));
Assert.assertEquals("TEST_TSL_LOCATION", this.configuration.getRegistry().get(ConfigurationParameter.TslLocation).get(0));
Assert.assertEquals("true", this.configuration.getRegistry().get(ConfigurationParameter.preferAiaOcsp).get(0));
+ Assert.assertEquals("73", this.configuration.getRegistry().get(ConfigurationParameter.ZipCompressionRatioCheckThreshold).get(0));
+ Assert.assertEquals("37", this.configuration.getRegistry().get(ConfigurationParameter.MaxAllowedZipCompressionRatio).get(0));
this.configuration.setTslLocation("Set TSL location");
this.configuration.setTspSource("Set TSP source");
diff --git a/digidoc4j/src/test/java/org/digidoc4j/DetachedXadesSignatureBuilderTest.java b/digidoc4j/src/test/java/org/digidoc4j/DetachedXadesSignatureBuilderTest.java
index 8248b76a8..6823f148c 100644
--- a/digidoc4j/src/test/java/org/digidoc4j/DetachedXadesSignatureBuilderTest.java
+++ b/digidoc4j/src/test/java/org/digidoc4j/DetachedXadesSignatureBuilderTest.java
@@ -33,7 +33,7 @@ public void signExternally() throws Exception {
byte[] signatureValue = pkcs12EccSignatureToken.sign(deserializedDataToSign.getDigestAlgorithm(), deserializedDataToSign.getDataToSign());
Signature signature = dataToSign.finalize(signatureValue);
assertTimestampSignature(signature);
- assertValidSignatureWithWarnings(signature);
+ assertValidSignature(signature);
}
@Test
@@ -47,7 +47,7 @@ public void signWithSignatureToken() throws Exception {
.invokeSigning();
assertTimestampSignature(signature);
- assertValidSignatureWithWarnings(signature);
+ assertValidSignature(signature);
}
@Test
@@ -79,7 +79,7 @@ public void signWithMultipleDataFiles() throws Exception {
.invokeSigning();
assertTimestampSignature(signature);
- assertValidSignatureWithWarnings(signature);
+ assertValidSignature(signature);
}
@Test
@@ -92,7 +92,7 @@ public void signWithNormalDataFile() {
.invokeSigning();
assertTimestampSignature(signature);
- assertValidSignatureWithWarnings(signature);
+ assertValidSignature(signature);
}
@Test(expected = InvalidDataFileException.class)
@@ -149,7 +149,7 @@ public void signWithLT_TMProfile() throws Exception {
.invokeSigningProcess();
assertTimemarkSignature(signature);
- assertValidSignatureWithWarnings(signature);
+ assertValidSignature(signature);
}
@Test
@@ -165,9 +165,8 @@ public void signWithB_EPESProfile() throws Exception {
assertBEpesSignature(signature);
ValidationResult validationResult = signature.validateSignature();
Assert.assertFalse(validationResult.isValid());
- Assert.assertEquals(2, validationResult.getWarnings().size());
+ Assert.assertEquals(1, validationResult.getWarnings().size());
Assert.assertEquals("The signature/seal is an INDETERMINATE AdES digital signature!", validationResult.getWarnings().get(0).getMessage());
- Assert.assertEquals("The authority info access is not present!", validationResult.getWarnings().get(1).getMessage());
Assert.assertEquals(2, validationResult.getErrors().size());
Assert.assertEquals("The result of the LTV validation process is not acceptable to continue the process!", validationResult.getErrors().get(0).getMessage());
Assert.assertEquals("No acceptable revocation data for the certificate!", validationResult.getErrors().get(1).getMessage());
@@ -185,7 +184,7 @@ public void signWithLTProfile() throws Exception {
.withSignatureProfile(SignatureProfile.LT)
.invokeSigningProcess();
assertTimestampSignature(signature);
- assertValidSignatureWithWarnings(signature);
+ assertValidSignature(signature);
}
@Test
@@ -226,7 +225,7 @@ public void signWithSignerInfo() throws Exception {
Assert.assertEquals("myRole / myResolution", signature.getSignerRoles().get(0));
Assert.assertEquals("SIGNATURE-1", signature.getId());
assertTimestampSignature(signature);
- assertValidSignatureWithWarnings(signature);
+ assertValidSignature(signature);
}
@Test
@@ -344,7 +343,7 @@ public void mimeTypeValueNotInitialized() throws Exception{
.invokeSigningProcess();
assertTimestampSignature(signature);
- assertValidSignatureWithWarnings(signature);
+ assertValidSignature(signature);
}
@Test
@@ -359,7 +358,7 @@ public void mimeTypeValueNotValidated() throws Exception {
.invokeSigningProcess();
assertTimestampSignature(signature);
- assertValidSignatureWithWarnings(signature);
+ assertValidSignature(signature);
}
@Test
@@ -377,7 +376,7 @@ public void addDetachedSignatureToContainer() throws Exception {
.invokeSigningProcess();
assertTimestampSignature(signature);
- assertValidSignatureWithWarnings(signature);
+ assertValidSignature(signature);
Container container = ContainerOpener.open(BDOC_WITH_TM_SIG, configuration);
container.addSignature(signature);
@@ -400,7 +399,7 @@ public void addDetachedSignatureToContainerWithNotMatchingMimeType_validationSho
.invokeSigningProcess();
assertTimestampSignature(signature);
- assertValidSignatureWithWarnings(signature);
+ assertValidSignature(signature);
Container container = ContainerOpener.open(BDOC_WITH_TM_SIG, configuration);
container.addSignature(signature);
diff --git a/digidoc4j/src/test/java/org/digidoc4j/impl/asic/AsicContainerParserZipBombingTest.java b/digidoc4j/src/test/java/org/digidoc4j/impl/asic/AsicContainerParserZipBombingTest.java
new file mode 100644
index 000000000..53ef5123c
--- /dev/null
+++ b/digidoc4j/src/test/java/org/digidoc4j/impl/asic/AsicContainerParserZipBombingTest.java
@@ -0,0 +1,104 @@
+package org.digidoc4j.impl.asic;
+
+import org.digidoc4j.AbstractTest;
+import org.digidoc4j.Configuration;
+import org.digidoc4j.exceptions.TechnicalException;
+import org.junit.Assert;
+import org.junit.Test;
+
+public abstract class AsicContainerParserZipBombingTest extends AbstractTest {
+
+ protected static final String MULTIPLE_DATAFILE_CONTAINER_PATH = "src/test/resources/testFiles/valid-containers/compression-ratio-46.55-with-8-datafiles.asice";
+ protected static final String SINGLE_DATAFILE_CONTAINER_PATH = "src/test/resources/testFiles/valid-containers/compression-ratio-51.91-with-1-datafile.asice";
+
+ protected static final long MULTIPLE_DATAFILE_CONTAINER_UNPACKED_SIZE_IN_BYTES = 546610L;
+ protected static final long SINGLE_DATAFILE_CONTAINER_UNPACKED_SIZE_IN_BYTES = 543348L;
+
+ protected static final long ONE_KILOBYTE_IN_BYTES = 1024L;
+
+ protected abstract AsicContainerParser createAsicContainerParserFromPath(String path, Configuration configuration);
+
+ @Test
+ public void testZipBombingDetectedWithSingleDataFileWhenUnpackRatioExceedsAllowedRatio() {
+ Configuration configuration = createTestConfigurationWithThresholdAndRatio(ONE_KILOBYTE_IN_BYTES, 51);
+ AsicContainerParser asicContainerParser = createAsicContainerParserFromPath(SINGLE_DATAFILE_CONTAINER_PATH, configuration);
+ assertReadThrowsZipBombingException(asicContainerParser);
+ }
+
+ @Test
+ public void testZipBombingNotDetectedWithSingleDataFileWhenUnpackRatioIsBelowAllowedRatio() {
+ Configuration configuration = createTestConfigurationWithThresholdAndRatio(ONE_KILOBYTE_IN_BYTES, 52);
+ AsicContainerParser asicContainerParser = createAsicContainerParserFromPath(SINGLE_DATAFILE_CONTAINER_PATH, configuration);
+ assertReadSucceeds(1, asicContainerParser);
+ }
+
+ @Test
+ public void testZipBombingNotDetectedWithSingleDataFileWhenUnpackRatioExceedsAllowedRatioButThresholdIsSlightlyAboveActualUnpackSize() {
+ Configuration configuration = createTestConfigurationWithThresholdAndRatio(SINGLE_DATAFILE_CONTAINER_UNPACKED_SIZE_IN_BYTES + 1, 51);
+ AsicContainerParser asicContainerParser = createAsicContainerParserFromPath(SINGLE_DATAFILE_CONTAINER_PATH, configuration);
+ assertReadSucceeds(1, asicContainerParser);
+ }
+
+ @Test
+ public void testZipBombingDetectedWithSingleDataFileWhenUnpackRatioExceedsAllowedRatioAndThresholdIsSlightlyBelowActualUnpackSize() {
+ Configuration configuration = createTestConfigurationWithThresholdAndRatio(SINGLE_DATAFILE_CONTAINER_UNPACKED_SIZE_IN_BYTES - 1, 51);
+ AsicContainerParser asicContainerParser = createAsicContainerParserFromPath(SINGLE_DATAFILE_CONTAINER_PATH, configuration);
+ assertReadThrowsZipBombingException(asicContainerParser);
+ }
+
+ @Test
+ public void testZipBombingDetectedWithMultipleDataFilesWhenUnpackRatioExceedsAllowedRatio() {
+ Configuration configuration = createTestConfigurationWithThresholdAndRatio(ONE_KILOBYTE_IN_BYTES, 46);
+ AsicContainerParser asicContainerParser = createAsicContainerParserFromPath(MULTIPLE_DATAFILE_CONTAINER_PATH, configuration);
+ assertReadThrowsZipBombingException(asicContainerParser);
+ }
+
+ @Test
+ public void testZipBombingNotDetectedWithMultipleDataFilesWhenUnpackRatioIsBelowAllowedRatio() {
+ Configuration configuration = createTestConfigurationWithThresholdAndRatio(ONE_KILOBYTE_IN_BYTES, 47);
+ AsicContainerParser asicContainerParser = createAsicContainerParserFromPath(MULTIPLE_DATAFILE_CONTAINER_PATH, configuration);
+ assertReadSucceeds(8, asicContainerParser);
+ }
+
+ @Test
+ public void testZipBombingNotDetectedWithMultipleDataFilesWhenUnpackRatioExceedsAllowedRatioButThresholdIsSlightlyAboveActualUnpackSize() {
+ Configuration configuration = createTestConfigurationWithThresholdAndRatio(MULTIPLE_DATAFILE_CONTAINER_UNPACKED_SIZE_IN_BYTES + 1, 46);
+ AsicContainerParser asicContainerParser = createAsicContainerParserFromPath(MULTIPLE_DATAFILE_CONTAINER_PATH, configuration);
+ assertReadSucceeds(8, asicContainerParser);
+ }
+
+ @Test
+ public void testZipBombingDetectedWithMultipleDataFilesWhenUnpackRatioExceedsAllowedRatioAndThresholdIsSlightlyBelowActualUnpackSize() {
+ Configuration configuration = createTestConfigurationWithThresholdAndRatio(MULTIPLE_DATAFILE_CONTAINER_UNPACKED_SIZE_IN_BYTES - 1, 46);
+ AsicContainerParser asicContainerParser = createAsicContainerParserFromPath(MULTIPLE_DATAFILE_CONTAINER_PATH, configuration);
+ assertReadThrowsZipBombingException(asicContainerParser);
+ }
+
+ protected static Configuration createTestConfigurationWithThresholdAndRatio(long threshold, int ratio) {
+ Configuration configuration = Configuration.of(Configuration.Mode.TEST);
+ configuration.setZipCompressionRatioCheckThresholdInBytes(threshold);
+ configuration.setMaxAllowedZipCompressionRatio(ratio);
+ return configuration;
+ }
+
+ protected static void assertReadSucceeds(int expectedDataFileCount, AsicContainerParser asicContainerParser) {
+ AsicParseResult asicParseResult = asicContainerParser.read();
+ Assert.assertNotNull(asicParseResult);
+ Assert.assertNotNull(asicParseResult.getSignatures());
+ Assert.assertEquals(1, asicParseResult.getSignatures().size());
+ Assert.assertNotNull(asicParseResult.getDataFiles());
+ Assert.assertEquals(expectedDataFileCount, asicParseResult.getDataFiles().size());
+ }
+
+ protected static void assertReadThrowsZipBombingException(AsicContainerParser asicContainerParser) {
+ TechnicalException caughtException = assertThrows(
+ TechnicalException.class,
+ asicContainerParser::read
+ );
+ Assert.assertEquals(
+ "Zip Bomb detected in the ZIP container. Validation is interrupted.",
+ caughtException.getMessage()
+ );
+ }
+
+}
diff --git a/digidoc4j/src/test/java/org/digidoc4j/impl/asic/AsicFileContainerParserZipBombingTest.java b/digidoc4j/src/test/java/org/digidoc4j/impl/asic/AsicFileContainerParserZipBombingTest.java
new file mode 100644
index 000000000..f64d0f9ff
--- /dev/null
+++ b/digidoc4j/src/test/java/org/digidoc4j/impl/asic/AsicFileContainerParserZipBombingTest.java
@@ -0,0 +1,12 @@
+package org.digidoc4j.impl.asic;
+
+import org.digidoc4j.Configuration;
+
+public class AsicFileContainerParserZipBombingTest extends AsicContainerParserZipBombingTest {
+
+ @Override
+ protected AsicContainerParser createAsicContainerParserFromPath(String path, Configuration configuration) {
+ return new AsicFileContainerParser(path, configuration);
+ }
+
+}
diff --git a/digidoc4j/src/test/java/org/digidoc4j/impl/asic/AsicStreamContainerParserZipBombingTest.java b/digidoc4j/src/test/java/org/digidoc4j/impl/asic/AsicStreamContainerParserZipBombingTest.java
new file mode 100644
index 000000000..34443abd4
--- /dev/null
+++ b/digidoc4j/src/test/java/org/digidoc4j/impl/asic/AsicStreamContainerParserZipBombingTest.java
@@ -0,0 +1,56 @@
+package org.digidoc4j.impl.asic;
+
+import org.digidoc4j.Configuration;
+import org.junit.Test;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+public class AsicStreamContainerParserZipBombingTest extends AsicContainerParserZipBombingTest {
+
+ @Override
+ protected AsicContainerParser createAsicContainerParserFromPath(String path, Configuration configuration) {
+ try {
+ return new AsicStreamContainerParser(new FileInputStream(path), configuration);
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to open stream for path: " + path, e);
+ }
+ }
+
+ /**
+ * NB: TEST OVERRIDDEN!!!
+ * When reading containers from an input stream, then the total size of the container is not known in advance and
+ * must be calculated on the fly while the stream is read. This makes the unpacked container contents size and the
+ * total container size ratio calculation inaccurate and can balloon the ratio way over the expected value if
+ * heavily compressed files are located at the beginning of the container.
+ * When reading containers from an input stream, then depending on the container and its contents, higher
+ * ZIP-bombing detection ratio and/or threshold might be needed to be configured.
+ */
+ @Test
+ @Override
+ public void testZipBombingNotDetectedWithSingleDataFileWhenUnpackRatioIsBelowAllowedRatio() {
+ // The ratio balloons approximately up to 600 before the actual container size in known and the ratio stabilizes
+ Configuration configuration = createTestConfigurationWithThresholdAndRatio(ONE_KILOBYTE_IN_BYTES, 600);
+ AsicContainerParser asicContainerParser = createAsicContainerParserFromPath(SINGLE_DATAFILE_CONTAINER_PATH, configuration);
+ assertReadSucceeds(1, asicContainerParser);
+ }
+
+ /**
+ * NB: TEST OVERRIDDEN!!!
+ * When reading containers from an input stream, then the total size of the container is not known in advance and
+ * must be calculated on the fly while the stream is read. This makes the unpacked container contents size and the
+ * total container size ratio calculation inaccurate and can balloon the ratio way over the expected value if
+ * heavily compressed files are located at the beginning of the container.
+ * When reading containers from an input stream, then depending on the container and its contents, higher
+ * ZIP-bombing detection ratio and/or threshold might be needed to be configured.
+ */
+ @Test
+ @Override
+ public void testZipBombingNotDetectedWithMultipleDataFilesWhenUnpackRatioIsBelowAllowedRatio() {
+ // The ratio balloons approximately up to 300 before the actual container size in known and the ratio stabilizes
+ Configuration configuration = createTestConfigurationWithThresholdAndRatio(ONE_KILOBYTE_IN_BYTES, 300);
+ AsicContainerParser asicContainerParser = createAsicContainerParserFromPath(MULTIPLE_DATAFILE_CONTAINER_PATH, configuration);
+ assertReadSucceeds(8, asicContainerParser);
+ }
+
+}
diff --git a/digidoc4j/src/test/java/org/digidoc4j/impl/asic/EntryValidatorTest.java b/digidoc4j/src/test/java/org/digidoc4j/impl/asic/EntryValidatorTest.java
deleted file mode 100644
index 11dd96cc0..000000000
--- a/digidoc4j/src/test/java/org/digidoc4j/impl/asic/EntryValidatorTest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package org.digidoc4j.impl.asic;
-
-import org.digidoc4j.Configuration;
-import org.junit.Test;
-
-import java.io.ByteArrayInputStream;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-
-public class EntryValidatorTest {
-
- @Test(expected = Test.None.class)
- public void emptyInput() throws IOException {
- ByteArrayInputStream inputStream = new ByteArrayInputStream("".getBytes(StandardCharsets.UTF_8));
- AsicStreamContainerParser containerParser = new AsicStreamContainerParser(inputStream, Configuration.of(Configuration.Mode.TEST));
- containerParser.parseContainer();
- AsicStreamContainerParser.EntryValidator validator = containerParser.new EntryValidator();
- validator.validate(0);
- }
-
- @Test(expected = IOException.class)
- public void emptyZipEntriesAndBigInput() throws IOException {
- ByteArrayInputStream inputStream = new ByteArrayInputStream("".getBytes(StandardCharsets.UTF_8));
- AsicStreamContainerParser containerParser = new AsicStreamContainerParser(inputStream, Configuration.of(Configuration.Mode.TEST));
- AsicStreamContainerParser.EntryValidator validator = containerParser.new EntryValidator();
- validator.validate(1000001);
- }
-
- @Test(expected = Test.None.class)
- public void potentialBloatingButNotExceedingMinimumThreshold() throws IOException {
- ByteArrayInputStream inputStream = new ByteArrayInputStream("".getBytes(StandardCharsets.UTF_8));
- AsicStreamContainerParser containerParser = new AsicStreamContainerParser(inputStream, Configuration.of(Configuration.Mode.TEST));
- AsicStreamContainerParser.EntryValidator validator = containerParser.new EntryValidator();
- validator.validate(1000000);
- }
-
- @Test(expected = Test.None.class)
- public void normalZipContainer() throws IOException {
- FileInputStream inputStream = new FileInputStream("src/test/resources/testFiles/valid-containers/valid-asice.asice");
- AsicStreamContainerParser containerParser = new AsicStreamContainerParser(inputStream, Configuration.of(Configuration.Mode.TEST));
- containerParser.parseContainer();
- }
-
- @Test(expected = IOException.class)
- public void unCompressedInputTooBig() throws IOException {
- FileInputStream inputStream = new FileInputStream("src/test/resources/testFiles/valid-containers/valid-asice.asice");
- AsicStreamContainerParser containerParser = new AsicStreamContainerParser(inputStream, Configuration.of(Configuration.Mode.TEST));
- containerParser.parseContainer();
- AsicStreamContainerParser.EntryValidator validator = containerParser.new EntryValidator();
- validator.validate(1000001);
- }
-
-}
diff --git a/digidoc4j/src/test/resources/testFiles/p12/sign_ECC_from_TEST_of_ESTEIDSK2015.p12 b/digidoc4j/src/test/resources/testFiles/p12/sign_ECC_from_TEST_of_ESTEIDSK2015.p12
new file mode 100644
index 000000000..6142ea8f5
Binary files /dev/null and b/digidoc4j/src/test/resources/testFiles/p12/sign_ECC_from_TEST_of_ESTEIDSK2015.p12 differ
diff --git a/digidoc4j/src/test/resources/testFiles/valid-containers/compression-ratio-46.55-with-8-datafiles.asice b/digidoc4j/src/test/resources/testFiles/valid-containers/compression-ratio-46.55-with-8-datafiles.asice
new file mode 100644
index 000000000..a789d3897
Binary files /dev/null and b/digidoc4j/src/test/resources/testFiles/valid-containers/compression-ratio-46.55-with-8-datafiles.asice differ
diff --git a/digidoc4j/src/test/resources/testFiles/valid-containers/compression-ratio-51.91-with-1-datafile.asice b/digidoc4j/src/test/resources/testFiles/valid-containers/compression-ratio-51.91-with-1-datafile.asice
new file mode 100644
index 000000000..5578ff6e4
Binary files /dev/null and b/digidoc4j/src/test/resources/testFiles/valid-containers/compression-ratio-51.91-with-1-datafile.asice differ
diff --git a/digidoc4j/src/test/resources/testFiles/yaml-configurations/digidoc_test_all_optional_settings.yaml b/digidoc4j/src/test/resources/testFiles/yaml-configurations/digidoc_test_all_optional_settings.yaml
index 636eb44bd..9abbaa8ee 100644
--- a/digidoc4j/src/test/resources/testFiles/yaml-configurations/digidoc_test_all_optional_settings.yaml
+++ b/digidoc4j/src/test/resources/testFiles/yaml-configurations/digidoc_test_all_optional_settings.yaml
@@ -41,6 +41,8 @@ ALLOWED_TS_AND_OCSP_RESPONSE_DELTA_IN_MINUTES: 1
SIGNATURE_PROFILE: LT_TM
SIGNATURE_DIGEST_ALGORITHM: SHA512
PREFER_AIA_OCSP: true
+ZIP_COMPRESSION_RATIO_CHECK_THRESHOLD_IN_BYTES: 73
+MAX_ALLOWED_ZIP_COMPRESSION_RATIO: 37
DIGIDOC_CAS:
- DIGIDOC_CA:
diff --git a/digidoc4j/src/test/resources/testFiles/yaml-configurations/digidoc_test_conf_invalid_zip_ratio.yaml b/digidoc4j/src/test/resources/testFiles/yaml-configurations/digidoc_test_conf_invalid_zip_ratio.yaml
new file mode 100644
index 000000000..a66de588d
--- /dev/null
+++ b/digidoc4j/src/test/resources/testFiles/yaml-configurations/digidoc_test_conf_invalid_zip_ratio.yaml
@@ -0,0 +1 @@
+MAX_ALLOWED_ZIP_COMPRESSION_RATIO: invalidValue
diff --git a/digidoc4j/src/test/resources/testFiles/yaml-configurations/digidoc_test_conf_invalid_zip_threshold.yaml b/digidoc4j/src/test/resources/testFiles/yaml-configurations/digidoc_test_conf_invalid_zip_threshold.yaml
new file mode 100644
index 000000000..8dd4334d9
--- /dev/null
+++ b/digidoc4j/src/test/resources/testFiles/yaml-configurations/digidoc_test_conf_invalid_zip_threshold.yaml
@@ -0,0 +1 @@
+ZIP_COMPRESSION_RATIO_CHECK_THRESHOLD_IN_BYTES: invalidValue
diff --git a/pom.xml b/pom.xml
index 6cb71bf25..2ba5f7071 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
org.digidoc4j
digidoc4j-parent
- 4.2.0
+ 4.2.1
pom
DigiDoc4J parent
@@ -141,8 +141,8 @@
1.8
1.8
1.8
- 1.7.30
- 1.68
+ 1.7.32
+ 1.69
none
diff --git a/publish.sh b/publish.sh
index 27692305e..70b733caf 100755
--- a/publish.sh
+++ b/publish.sh
@@ -1,6 +1,6 @@
#!/bin/bash
-version="4.2.0"
+version="4.2.1"
staging_url="https://oss.sonatype.org/service/local/staging/deploy/maven2/"
repositoryId="ossrh"