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 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"