diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 721c50375..c0dda1121 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 52690 - added a getter for length of encrypted data in Ecma and Agile decryptors 52255 - support adding TIFF,EPS and WPG pictures in OOXML documents 52078 - avoid OutOfMemoryError when rendering groupped pictures in HSLF 52745 - fixed XSSFRichtextString.append to preserve leading / trailing spaces diff --git a/src/java/org/apache/poi/poifs/crypt/AgileDecryptor.java b/src/java/org/apache/poi/poifs/crypt/AgileDecryptor.java index 17ef47bb8..88e5f887f 100644 --- a/src/java/org/apache/poi/poifs/crypt/AgileDecryptor.java +++ b/src/java/org/apache/poi/poifs/crypt/AgileDecryptor.java @@ -19,24 +19,18 @@ package org.apache.poi.poifs.crypt; import java.util.Arrays; import java.io.IOException; import java.io.InputStream; -import java.io.FilterInputStream; -import java.io.ByteArrayInputStream; import java.security.MessageDigest; import java.security.GeneralSecurityException; import java.security.NoSuchAlgorithmException; -import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.EncryptedDocumentException; import javax.crypto.Cipher; -import javax.crypto.CipherInputStream; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.IvParameterSpec; -import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.DocumentInputStream; -import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.util.LittleEndian; /** @@ -46,6 +40,7 @@ public class AgileDecryptor extends Decryptor { private final EncryptionInfo _info; private SecretKey _secretKey; + private long _length = -1; private static final byte[] kVerifierInputBlock; private static final byte[] kHashedVerifierBlock; @@ -104,8 +99,13 @@ public class AgileDecryptor extends Decryptor { public InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException { DocumentInputStream dis = dir.createDocumentInputStream("EncryptedPackage"); - long size = dis.readLong(); - return new ChunkedCipherInputStream(dis, size); + _length = dis.readLong(); + return new ChunkedCipherInputStream(dis, _length); + } + + public long getLength(){ + if(_length == -1) throw new IllegalStateException("EcmaDecryptor.getDataStream() was not called"); + return _length; } protected AgileDecryptor(EncryptionInfo info) { @@ -182,7 +182,7 @@ public class AgileDecryptor extends Decryptor { private byte[] nextChunk() throws GeneralSecurityException, IOException { int index = (int)(_pos >> 12); byte[] blockKey = new byte[4]; - LittleEndian.putInt(blockKey, index); + LittleEndian.putInt(blockKey, 0, index); byte[] iv = generateIv(_info.getHeader().getAlgorithm(), _info.getHeader().getKeySalt(), blockKey); _cipher.init(Cipher.DECRYPT_MODE, _secretKey, new IvParameterSpec(iv)); diff --git a/src/java/org/apache/poi/poifs/crypt/Decryptor.java b/src/java/org/apache/poi/poifs/crypt/Decryptor.java index fb5b11f7b..f7ed16f87 100644 --- a/src/java/org/apache/poi/poifs/crypt/Decryptor.java +++ b/src/java/org/apache/poi/poifs/crypt/Decryptor.java @@ -31,12 +31,40 @@ import org.apache.poi.util.LittleEndian; public abstract class Decryptor { public static final String DEFAULT_PASSWORD="VelvetSweatshop"; + /** + * Return a stream with decrypted data. + *

+ * Use {@link #getLength()} to get the size of that data that can be safely read from the stream. + * Just reading to the end of the input stream is not sufficient because there are + * normally padding bytes that must be discarded + *

+ * + * @param dir the node to read from + * @return decrypted stream + */ public abstract InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException; public abstract boolean verifyPassword(String password) throws GeneralSecurityException; + /** + * Returns the length of the encytpted data that can be safely read with + * {@link #getDataStream(org.apache.poi.poifs.filesystem.DirectoryNode)}. + * Just reading to the end of the input stream is not sufficient because there are + * normally padding bytes that must be discarded + * + *

+ * The length variable is initialized in {@link #getDataStream(org.apache.poi.poifs.filesystem.DirectoryNode)}, + * an attempt to call getLength() prior to getDataStream() will result in IllegalStateException. + *

+ * + * @return length of the encrypted data + * @throws IllegalStateException if {@link #getDataStream(org.apache.poi.poifs.filesystem.DirectoryNode)} + * was not called + */ + public abstract long getLength(); + public static Decryptor getInstance(EncryptionInfo info) { int major = info.getVersionMajor(); int minor = info.getVersionMinor(); @@ -82,7 +110,7 @@ public abstract class Decryptor { for (int i = 0; i < info.getVerifier().getSpinCount(); i++) { sha1.reset(); - LittleEndian.putInt(iterator, i); + LittleEndian.putInt(iterator, 0, i); sha1.update(iterator); hash = sha1.digest(hash); } diff --git a/src/java/org/apache/poi/poifs/crypt/EcmaDecryptor.java b/src/java/org/apache/poi/poifs/crypt/EcmaDecryptor.java index 441a4335f..641ee8cb4 100644 --- a/src/java/org/apache/poi/poifs/crypt/EcmaDecryptor.java +++ b/src/java/org/apache/poi/poifs/crypt/EcmaDecryptor.java @@ -18,7 +18,6 @@ package org.apache.poi.poifs.crypt; import java.io.IOException; import java.io.InputStream; -import java.io.UnsupportedEncodingException; import java.security.GeneralSecurityException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -31,7 +30,6 @@ import javax.crypto.spec.SecretKeySpec; import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.DocumentInputStream; -import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.util.LittleEndian; /** @@ -41,6 +39,7 @@ import org.apache.poi.util.LittleEndian; public class EcmaDecryptor extends Decryptor { private final EncryptionInfo info; private byte[] passwordHash; + private long _length = -1; public EcmaDecryptor(EncryptionInfo info) { this.info = info; @@ -51,7 +50,7 @@ public class EcmaDecryptor extends Decryptor { sha1.update(passwordHash); byte[] blockValue = new byte[4]; - LittleEndian.putInt(blockValue, block); + LittleEndian.putInt(blockValue, 0, block); byte[] finalHash = sha1.digest(blockValue); int requiredKeyLength = info.getHeader().getKeySize()/8; @@ -125,8 +124,13 @@ public class EcmaDecryptor extends Decryptor { public InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException { DocumentInputStream dis = dir.createDocumentInputStream("EncryptedPackage"); - long size = dis.readLong(); + _length = dis.readLong(); return new CipherInputStream(dis, getCipher()); } + + public long getLength(){ + if(_length == -1) throw new IllegalStateException("EcmaDecryptor.getDataStream() was not called"); + return _length; + } } diff --git a/src/testcases/org/apache/poi/poifs/crypt/TestDecryptor.java b/src/testcases/org/apache/poi/poifs/crypt/TestDecryptor.java index 27385719c..dcef8d31c 100644 --- a/src/testcases/org/apache/poi/poifs/crypt/TestDecryptor.java +++ b/src/testcases/org/apache/poi/poifs/crypt/TestDecryptor.java @@ -20,7 +20,9 @@ import junit.framework.TestCase; import org.apache.poi.POIDataSamples; import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.security.GeneralSecurityException; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -80,4 +82,36 @@ public class TestDecryptor extends TestCase { } } } + public void testDataLength() throws Exception { + POIFSFileSystem fs = new POIFSFileSystem(POIDataSamples.getPOIFSInstance().openResourceAsStream("protected_agile.docx")); + + EncryptionInfo info = new EncryptionInfo(fs); + + Decryptor d = Decryptor.getInstance(info); + + d.verifyPassword(Decryptor.DEFAULT_PASSWORD); + + InputStream is = d.getDataStream(fs); + + long len = d.getLength(); + assertEquals(12810, len); + + byte[] buf = new byte[(int)len]; + + is.read(buf); + + ZipInputStream zin = new ZipInputStream(new ByteArrayInputStream(buf)); + + while (true) { + ZipEntry entry = zin.getNextEntry(); + if (entry==null) { + break; + } + + while (zin.available()>0) { + zin.skip(zin.available()); + } + } + } + }