[Git][java-team/zip4j][upstream] New upstream version 2.11.3

Andrius Merkys (@merkys) gitlab at salsa.debian.org
Mon Jan 30 11:52:31 GMT 2023



Andrius Merkys pushed to branch upstream at Debian Java Maintainers / zip4j


Commits:
47338ff9 by Andrius Merkys at 2023-01-30T02:39:21-05:00
New upstream version 2.11.3
- - - - -


13 changed files:

- README.md
- pom.xml
- src/main/java/net/lingala/zip4j/crypto/AESDecrypter.java
- src/main/java/net/lingala/zip4j/crypto/PBKDF2/MacBasedPRF.java
- src/main/java/net/lingala/zip4j/io/inputstream/AesCipherInputStream.java
- src/main/java/net/lingala/zip4j/io/inputstream/CipherInputStream.java
- src/main/java/net/lingala/zip4j/io/inputstream/DecompressedInputStream.java
- src/main/java/net/lingala/zip4j/io/inputstream/InflaterInputStream.java
- src/main/java/net/lingala/zip4j/io/inputstream/ZipInputStream.java
- src/test/java/net/lingala/zip4j/MiscZipFileIT.java
- src/test/java/net/lingala/zip4j/io/inputstream/ZipInputStreamIT.java
- src/test/java/net/lingala/zip4j/io/outputstream/ZipOutputStreamIT.java
- + src/test/resources/test-archives/aes_with_extra_data_record_and_corrupt_mac.zip


Changes:

=====================================
README.md
=====================================
@@ -71,7 +71,7 @@ Zip4j supports JDK 7 as well. In cases where the feature/class from JDK 8 is mis
 <dependency>
     <groupId>net.lingala.zip4j</groupId>
     <artifactId>zip4j</artifactId>
-    <version>2.11.2</version>
+    <version>2.11.3</version>
 </dependency>
 ```
 


=====================================
pom.xml
=====================================
@@ -6,7 +6,7 @@
 
     <groupId>net.lingala.zip4j</groupId>
     <artifactId>zip4j</artifactId>
-    <version>2.11.2</version>
+    <version>2.11.4-SNAPSHOT</version>
 
     <name>Zip4j</name>
     <description>Zip4j - A Java library for zip files and streams</description>


=====================================
src/main/java/net/lingala/zip4j/crypto/AESDecrypter.java
=====================================
@@ -86,7 +86,7 @@ public class AESDecrypter implements Decrypter {
     return len;
   }
 
-  public byte[] getCalculatedAuthenticationBytes() {
-    return mac.doFinal();
+  public byte[] getCalculatedAuthenticationBytes(int numberOfBytesPushedBack) {
+    return mac.doFinal(numberOfBytesPushedBack);
   }
 }


=====================================
src/main/java/net/lingala/zip4j/crypto/PBKDF2/MacBasedPRF.java
=====================================
@@ -16,11 +16,16 @@
 
 package net.lingala.zip4j.crypto.PBKDF2;
 
+import net.lingala.zip4j.util.InternalZipConstants;
+
 import javax.crypto.Mac;
 import javax.crypto.spec.SecretKeySpec;
+import java.io.ByteArrayOutputStream;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 
+import static net.lingala.zip4j.util.InternalZipConstants.AES_BLOCK_SIZE;
+
 /*
  * Source referred from Matthias Gartner's PKCS#5 implementation -
  * see http://rtner.de/software/PBKDF2.html
@@ -30,9 +35,11 @@ public class MacBasedPRF implements PRF {
   private Mac mac;
   private int hLen;
   private String macAlgorithm;
+  private ByteArrayOutputStream macCache;
 
   public MacBasedPRF(String macAlgorithm) {
     this.macAlgorithm = macAlgorithm;
+    this.macCache = new ByteArrayOutputStream(InternalZipConstants.BUFF_SIZE);
     try {
       mac = Mac.getInstance(macAlgorithm);
       hLen = mac.getMacLength();
@@ -42,10 +49,20 @@ public class MacBasedPRF implements PRF {
   }
 
   public byte[] doFinal(byte[] M) {
+    if (macCache.size() > 0) {
+      doMacUpdate(0);
+    }
     return mac.doFinal(M);
   }
 
   public byte[] doFinal() {
+    return doFinal(0);
+  }
+
+  public byte[] doFinal(int numberOfBytesToPushbackForMac) {
+    if (macCache.size() > 0) {
+      doMacUpdate(numberOfBytesToPushbackForMac);
+    }
     return mac.doFinal();
   }
 
@@ -61,19 +78,29 @@ public class MacBasedPRF implements PRF {
     }
   }
 
-  public void update(byte[] U) {
+  public void update(byte[] u) {
+    update(u, 0, u.length);
+  }
+
+  public void update(byte[] u, int start, int len) {
     try {
-      mac.update(U);
+      if (macCache.size() + len > InternalZipConstants.BUFF_SIZE) {
+        doMacUpdate(0);
+      }
+      macCache.write(u, start, len);
     } catch (IllegalStateException e) {
       throw new RuntimeException(e);
     }
   }
 
-  public void update(byte[] U, int start, int len) {
-    try {
-      mac.update(U, start, len);
-    } catch (IllegalStateException e) {
-      throw new RuntimeException(e);
+  private void doMacUpdate(int numberOfBytesToPushBack) {
+    byte[] macBytes = macCache.toByteArray();
+    int numberOfBytesToRead = macBytes.length - numberOfBytesToPushBack;
+    int updateLength;
+    for (int i = 0; i < numberOfBytesToRead; i += InternalZipConstants.AES_BLOCK_SIZE) {
+      updateLength = (i + AES_BLOCK_SIZE) <= numberOfBytesToRead ? AES_BLOCK_SIZE : numberOfBytesToRead - i;
+      mac.update(macBytes, i, updateLength);
     }
+    macCache.reset();
   }
 }


=====================================
src/main/java/net/lingala/zip4j/io/inputstream/AesCipherInputStream.java
=====================================
@@ -4,9 +4,7 @@ import net.lingala.zip4j.crypto.AESDecrypter;
 import net.lingala.zip4j.exception.ZipException;
 import net.lingala.zip4j.model.AESExtraDataRecord;
 import net.lingala.zip4j.model.LocalFileHeader;
-import net.lingala.zip4j.model.enums.CompressionMethod;
 import net.lingala.zip4j.util.InternalZipConstants;
-import net.lingala.zip4j.util.Zip4jUtil;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -119,22 +117,12 @@ class AesCipherInputStream extends CipherInputStream<AESDecrypter> {
   }
 
   @Override
-  protected void endOfEntryReached(InputStream inputStream) throws IOException {
-    verifyContent(readStoredMac(inputStream));
+  protected void endOfEntryReached(InputStream inputStream, int numberOfBytesPushedBack) throws IOException {
+    verifyContent(readStoredMac(inputStream), numberOfBytesPushedBack);
   }
 
-  private void verifyContent(byte[] storedMac) throws IOException {
-    if (getLocalFileHeader().isDataDescriptorExists()
-        && CompressionMethod.DEFLATE.equals(Zip4jUtil.getCompressionMethod(getLocalFileHeader()))) {
-      // Skip content verification in case of Deflate compression and if data descriptor exists.
-      // In this case, we do not know the exact size of compressed data before hand and it is possible that we read
-      // and pass more than required data into inflater, thereby corrupting the aes mac bytes.
-      // See usage of PushBackInputStream in the project for how this push back of data is done
-      // Unfortunately, in this case we cannot perform a content verification and have to skip
-      return;
-    }
-
-    byte[] calculatedMac = getDecrypter().getCalculatedAuthenticationBytes();
+  private void verifyContent(byte[] storedMac, int numberOfBytesPushedBack) throws IOException {
+    byte[] calculatedMac = getDecrypter().getCalculatedAuthenticationBytes(numberOfBytesPushedBack);
     byte[] first10BytesOfCalculatedMac = new byte[AES_AUTH_LENGTH];
     System.arraycopy(calculatedMac, 0, first10BytesOfCalculatedMac, 0, InternalZipConstants.AES_AUTH_LENGTH);
 


=====================================
src/main/java/net/lingala/zip4j/io/inputstream/CipherInputStream.java
=====================================
@@ -80,7 +80,7 @@ abstract class CipherInputStream<T extends Decrypter> extends InputStream {
     return decrypter;
   }
 
-  protected void endOfEntryReached(InputStream inputStream) throws IOException {
+  protected void endOfEntryReached(InputStream inputStream, int numberOfBytesPushedBack) throws IOException {
     // is optional but useful for AES
   }
 


=====================================
src/main/java/net/lingala/zip4j/io/inputstream/DecompressedInputStream.java
=====================================
@@ -39,12 +39,13 @@ abstract class DecompressedInputStream extends InputStream {
     cipherInputStream.close();
   }
 
-  public void endOfEntryReached(InputStream inputStream) throws IOException {
-    cipherInputStream.endOfEntryReached(inputStream);
+  public void endOfEntryReached(InputStream inputStream, int numberOfBytesPushedBack) throws IOException {
+    cipherInputStream.endOfEntryReached(inputStream, numberOfBytesPushedBack);
   }
 
-  public void pushBackInputStreamIfNecessary(PushbackInputStream pushbackInputStream) throws IOException {
+  public int pushBackInputStreamIfNecessary(PushbackInputStream pushbackInputStream) throws IOException {
     // Do nothing by default
+    return 0;
   }
 
   protected byte[] getLastReadRawDataCache() {


=====================================
src/main/java/net/lingala/zip4j/io/inputstream/InflaterInputStream.java
=====================================
@@ -55,21 +55,22 @@ public class InflaterInputStream extends DecompressedInputStream {
   }
 
   @Override
-  public void endOfEntryReached(InputStream inputStream) throws IOException {
+  public void endOfEntryReached(InputStream inputStream, int numberOfBytesPushedBack) throws IOException {
     if (inflater != null) {
       inflater.end();
       inflater = null;
     }
-    super.endOfEntryReached(inputStream);
+    super.endOfEntryReached(inputStream, numberOfBytesPushedBack);
   }
 
   @Override
-  public void pushBackInputStreamIfNecessary(PushbackInputStream pushbackInputStream) throws IOException {
+  public int pushBackInputStreamIfNecessary(PushbackInputStream pushbackInputStream) throws IOException {
     int n = inflater.getRemaining();
     if (n > 0) {
       byte[] rawDataCache = getLastReadRawDataCache();
       pushbackInputStream.unread(rawDataCache, len - n, n);
     }
+    return n;
   }
 
   @Override


=====================================
src/main/java/net/lingala/zip4j/io/inputstream/ZipInputStream.java
=====================================
@@ -231,10 +231,10 @@ public class ZipInputStream extends InputStream {
   private void endOfCompressedDataReached() throws IOException {
     //With inflater, without knowing the compressed or uncompressed size, we over read necessary data
     //In such cases, we have to push back the inputstream to the end of data
-    decompressedInputStream.pushBackInputStreamIfNecessary(inputStream);
+    int numberOfBytesPushedBack = decompressedInputStream.pushBackInputStreamIfNecessary(inputStream);
 
     //First signal the end of data for this entry so that ciphers can read any header data if applicable
-    decompressedInputStream.endOfEntryReached(inputStream);
+    decompressedInputStream.endOfEntryReached(inputStream, numberOfBytesPushedBack);
 
     readExtendedLocalFileHeaderIfPresent();
     verifyCrc();


=====================================
src/test/java/net/lingala/zip4j/MiscZipFileIT.java
=====================================
@@ -673,6 +673,16 @@ public class MiscZipFileIT extends AbstractIT {
     verifyLastModifiedFileTime(zipFile, fileToTestWith, expectedLastModifiedTimeInMillis);
   }
 
+  @Test
+  public void testExtractFileWithExtraDataRecordAndCorruptMac() throws ZipException {
+    ZipFile zipFile = new ZipFile(getTestArchiveFromResources("aes_with_extra_data_record_and_corrupt_mac.zip"), PASSWORD);
+
+    expectedException.expect(ZipException.class);
+    expectedException.expectMessage("java.io.IOException: Reached end of data for this entry, but aes verification failed");
+
+    zipFile.extractAll(outputFolder.getPath());
+  }
+
   private void testAddAndExtractWithPasswordUtf8Encoding(boolean useUtf8ForPassword) throws IOException {
     char[] password = "hun 焰".toCharArray();
     ZipFile zipFile = new ZipFile(generatedZipFile, password);


=====================================
src/test/java/net/lingala/zip4j/io/inputstream/ZipInputStreamIT.java
=====================================
@@ -24,10 +24,10 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.file.Files;
+import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
-import java.security.SecureRandom;
 import java.util.Set;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
@@ -351,6 +351,15 @@ public class ZipInputStreamIT extends AbstractIT {
     }
   }
 
+  @Test
+  public void testExtractZipFileWithExtraDataRecordAndCorruptAesMacFails() throws IOException {
+    expectedException.expect(IOException.class);
+    expectedException.expectMessage("Reached end of data for this entry, but aes verification failed");
+
+    extractZipFileWithInputStreams(TestUtils.getTestArchiveFromResources("aes_with_extra_data_record_and_corrupt_mac.zip"),
+            PASSWORD, InternalZipConstants.BUFF_SIZE, false, 1);
+  }
+
   private void extractZipFileWithInputStreams(File zipFile, char[] password) throws IOException {
     extractZipFileWithInputStreams(zipFile, password, InternalZipConstants.BUFF_SIZE);
   }


=====================================
src/test/java/net/lingala/zip4j/io/outputstream/ZipOutputStreamIT.java
=====================================
@@ -30,6 +30,8 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -310,6 +312,28 @@ public class ZipOutputStreamIT extends AbstractIT {
     }
   }
 
+  @Test
+  public void testZipInputStreamWithDeflateAndAesEncryption() throws IOException {
+    byte[] buffer = new byte[InternalZipConstants.BUFF_SIZE];
+    int readLen;
+    File fileToAdd = getTestFileFromResources("file_PDF_1MB.pdf");
+    try (ZipOutputStream zipOutputStream = new ZipOutputStream(Files.newOutputStream(generatedZipFile.toPath()), PASSWORD)) {
+      ZipParameters zipParameters = new ZipParameters();
+      zipParameters.setFileNameInZip(fileToAdd.getName());
+      zipParameters.setEncryptFiles(true);
+      zipParameters.setEncryptionMethod(EncryptionMethod.AES);
+      zipOutputStream.putNextEntry(zipParameters);
+      try (InputStream inputStream = Files.newInputStream(fileToAdd.toPath())) {
+        while ((readLen = inputStream.read(buffer)) != -1) {
+          zipOutputStream.write(buffer, 0, readLen);
+        }
+      }
+    }
+
+    verifyZipFileByExtractingAllFiles(generatedZipFile, PASSWORD, outputFolder, 1, true);
+    extractZipFileWithInputStream(generatedZipFile);
+  }
+
   private void testZipOutputStream(CompressionMethod compressionMethod, boolean encrypt,
                                    EncryptionMethod encryptionMethod, AesKeyStrength aesKeyStrength,
                                    AesVersion aesVersion)
@@ -513,4 +537,19 @@ public class ZipOutputStreamIT extends AbstractIT {
     zipOutputStream.putNextEntry(zipParameters);
     zipOutputStream.closeEntry();
   }
+
+  private void extractZipFileWithInputStream(File zipFile) throws IOException {
+    byte[] buffer = new byte[InternalZipConstants.BUFF_SIZE];
+    int readLen;
+    LocalFileHeader lfh;
+    try (ZipInputStream zipInputStream = new ZipInputStream(Files.newInputStream(zipFile.toPath()), PASSWORD)) {
+      while ((lfh = zipInputStream.getNextEntry()) != null) {
+        while ((readLen = zipInputStream.read(buffer)) != -1) {
+          try (OutputStream outputStream = Files.newOutputStream(Paths.get(outputFolder.getPath(), lfh.getFileName()))) {
+            outputStream.write(buffer, 0, readLen);
+          }
+        }
+      }
+    }
+  }
 }


=====================================
src/test/resources/test-archives/aes_with_extra_data_record_and_corrupt_mac.zip
=====================================
Binary files /dev/null and b/src/test/resources/test-archives/aes_with_extra_data_record_and_corrupt_mac.zip differ



View it on GitLab: https://salsa.debian.org/java-team/zip4j/-/commit/47338ff98a29b74fa3509f2e09c036683142fd73

-- 
View it on GitLab: https://salsa.debian.org/java-team/zip4j/-/commit/47338ff98a29b74fa3509f2e09c036683142fd73
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20230130/9d4905c9/attachment.htm>


More information about the pkg-java-commits mailing list