Repository: commons-compress Updated Branches: refs/heads/master 53f6d42f7 -> 22b0d0629
COMPRESS-455 handle APK Signing Block Project: http://git-wip-us.apache.org/repos/asf/commons-compress/repo Commit: http://git-wip-us.apache.org/repos/asf/commons-compress/commit/af6fe141 Tree: http://git-wip-us.apache.org/repos/asf/commons-compress/tree/af6fe141 Diff: http://git-wip-us.apache.org/repos/asf/commons-compress/diff/af6fe141 Branch: refs/heads/master Commit: af6fe141036d30bfd1613758b7a9fb413bf2bafc Parents: 53f6d42 Author: Stefan Bodewig <bode...@apache.org> Authored: Sun Jul 1 13:45:56 2018 +0200 Committer: Stefan Bodewig <bode...@apache.org> Committed: Sun Jul 1 13:45:56 2018 +0200 ---------------------------------------------------------------------- src/changes/changes.xml | 5 ++ .../archivers/zip/ZipArchiveInputStream.java | 71 ++++++++++++++++++-- 2 files changed, 72 insertions(+), 4 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/commons-compress/blob/af6fe141/src/changes/changes.xml ---------------------------------------------------------------------- diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 5a620cb..82cf229 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -59,6 +59,11 @@ The <action> type attribute can be add,update,fix,remove. ensure all held resources get closed even if exceptions are thrown during the closing the stream. </action> + <action issue="COMPRESS-455" type="fix" date="2018-07-01"> + ZipArchiveInputStream can now detect the APK Signing Block + used in signed Android APK files and treats it as an "end of + archive" marker. + </action> </release> <release version="1.17" date="2018-06-03" description="Release 1.17"> http://git-wip-us.apache.org/repos/asf/commons-compress/blob/af6fe141/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java index 729d92e..77d6d70 100644 --- a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java +++ b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java @@ -24,7 +24,9 @@ import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.PushbackInputStream; +import java.math.BigInteger; import java.nio.ByteBuffer; +import java.util.Arrays; import java.util.zip.CRC32; import java.util.zip.DataFormatException; import java.util.zip.Inflater; @@ -249,12 +251,12 @@ public class ZipArchiveInputStream extends ArchiveInputStream implements InputSt } final ZipLong sig = new ZipLong(lfhBuf); - if (sig.equals(ZipLong.CFH_SIG) || sig.equals(ZipLong.AED_SIG)) { + if (!sig.equals(ZipLong.LFH_SIG)) { + if (sig.equals(ZipLong.CFH_SIG) || sig.equals(ZipLong.AED_SIG) || isApkSigningBlock(lfhBuf)) { hitCentralDirectory = true; skipRemainderOfArchive(); return null; } - if (!sig.equals(ZipLong.LFH_SIG)) { throw new ZipException(String.format("Unexpected record signature: 0X%X", sig.getValue())); } @@ -789,9 +791,14 @@ public class ZipArchiveInputStream extends ArchiveInputStream implements InputSt } private void readFully(final byte[] b) throws IOException { - final int count = IOUtils.readFully(in, b); + readFully(b, 0); + } + + private void readFully(final byte[] b, final int off) throws IOException { + final int len = b.length - off; + final int count = IOUtils.readFully(in, b, off, len); count(count); - if (count < b.length) { + if (count < len) { throw new EOFException(); } } @@ -1087,6 +1094,62 @@ public class ZipArchiveInputStream extends ArchiveInputStream implements InputSt return b == ZipArchiveOutputStream.EOCD_SIG[0]; } + private static final byte[] APK_SIGNING_BLOCK_MAGIC = new byte[] { + 'A', 'P', 'K', ' ', 'S', 'i', 'g', ' ', 'B', 'l', 'o', 'c', 'k', ' ', '4', '2', + }; + private static final BigInteger LONG_MAX = BigInteger.valueOf(Long.MAX_VALUE); + + /** + * Checks whether this might be an APK Signing Block. + * + * <p>Unfortunately the APK signing block does not start with some kind of signature, it rather ends with one. It + * starts with a length, so what we do is parse the suspect length, skip ahead far enough, look for the signature + * and if we've found it, return true.</p> + * + * @param suspectLocalFileHeader the bytes read from the underlying stream in the expectation that they would hold + * the local file header of the next entry. + * + * @return true if this looks like a APK signing block + * + * @see <a href="https://source.android.com/security/apksigning/v2">https://source.android.com/security/apksigning/v2</a> + */ + private boolean isApkSigningBlock(byte[] suspectLocalFileHeader) throws IOException { + // length of block excluding the size field itself + BigInteger len = ZipEightByteInteger.getValue(suspectLocalFileHeader); + // LFH has already been read and all but the first eight bytes contain (part of) the APK signing block, + // also subtract 16 bytes in order to position us at the magic string + BigInteger toSkip = len.add(BigInteger.valueOf(DWORD - suspectLocalFileHeader.length + - APK_SIGNING_BLOCK_MAGIC.length)); + byte[] magic = new byte[APK_SIGNING_BLOCK_MAGIC.length]; + + try { + if (toSkip.signum() < 0) { + // suspectLocalFileHeader contains the start of suspect magic string + int off = suspectLocalFileHeader.length + toSkip.intValue(); + // length was shorter than magic length + if (off < DWORD) { + return false; + } + int bytesInBuffer = Math.abs(toSkip.intValue()); + System.arraycopy(suspectLocalFileHeader, off, magic, 0, Math.min(bytesInBuffer, magic.length)); + if (bytesInBuffer < magic.length) { + readFully(magic, bytesInBuffer); + } + } else { + while (toSkip.compareTo(LONG_MAX) > 0) { + realSkip(Long.MAX_VALUE); + toSkip = toSkip.add(LONG_MAX.negate()); + } + realSkip(toSkip.longValue()); + readFully(magic); + } + } catch (EOFException ex) { + // length was invalid + return false; + } + return Arrays.equals(magic, APK_SIGNING_BLOCK_MAGIC); + } + /** * Structure collecting information for the entry that is * currently being read.