From 2b4f9448830a532c91d9c41f67241ae89debdcf1 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Tue, 6 Jun 2017 22:21:11 +0000 Subject: [PATCH] #61162 - En-/decryption support for HWPF Decryption for Binary RC4 and CryptoAPI (... XOR is missing) git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1797837 13f79535-47bb-0310-9956-ffa450edef68 --- src/java/org/apache/poi/POIDocument.java | 2 +- .../record/crypto/Biff8EncryptionKey.java | 6 +- .../poi/poifs/crypt/EncryptionInfo.java | 13 +- .../crypt/binaryrc4/BinaryRC4Decryptor.java | 6 +- .../src/org/apache/poi/hwpf/HWPFDocument.java | 121 +++--- .../org/apache/poi/hwpf/HWPFDocumentCore.java | 361 +++++++++++------- .../apache/poi/hwpf/HWPFTestEncryption.java | 69 ++++ .../document/password_password_cryptoapi.doc | Bin 0 -> 27136 bytes .../document/password_tika_binaryrc4.doc | Bin 0 -> 22016 bytes 9 files changed, 378 insertions(+), 200 deletions(-) create mode 100644 src/scratchpad/testcases/org/apache/poi/hwpf/HWPFTestEncryption.java create mode 100644 test-data/document/password_password_cryptoapi.doc create mode 100644 test-data/document/password_tika_binaryrc4.doc diff --git a/src/java/org/apache/poi/POIDocument.java b/src/java/org/apache/poi/POIDocument.java index dc626da49..774507722 100644 --- a/src/java/org/apache/poi/POIDocument.java +++ b/src/java/org/apache/poi/POIDocument.java @@ -195,7 +195,7 @@ public abstract class POIDocument implements Closeable { NPOIFSFileSystem encPoifs = null; String step = "getting"; try { - if (encryptionInfo != null) { + if (encryptionInfo != null && encryptionInfo.isDocPropsEncrypted()) { step = "getting encrypted"; String encryptedStream = null; for (String s : encryptedStreamNames) { diff --git a/src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.java b/src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.java index 382ae2f13..f589f02cb 100644 --- a/src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.java +++ b/src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.java @@ -32,7 +32,11 @@ public final class Biff8EncryptionKey { * @param password pass null to clear user password (and use default) */ public static void setCurrentUserPassword(String password) { - _userPasswordTLS.set(password); + if (password == null) { + _userPasswordTLS.remove(); + } else { + _userPasswordTLS.set(password); + } } /** diff --git a/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java b/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java index e8895c1ab..c70105fb9 100644 --- a/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java +++ b/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java @@ -122,8 +122,11 @@ public class EncryptionInfo implements Cloneable { } else if ( 2 <= versionMajor && versionMajor <= 4 && versionMinor == 2) { - encryptionMode = (preferredEncryptionMode == cryptoAPI) ? cryptoAPI : standard; encryptionFlags = dis.readInt(); + encryptionMode = ( + preferredEncryptionMode == cryptoAPI + || !flagAES.isSet(encryptionFlags)) + ? cryptoAPI : standard; } else if ( versionMajor == agile.versionMajor && versionMinor == agile.versionMinor){ @@ -268,6 +271,14 @@ public class EncryptionInfo implements Cloneable { return encryptionMode; } + /** + * @return true, if Document Summary / Summary are encrypted and stored in the {@code EncryptedStream} stream, + * otherwise the Summaries aren't encrypted and located in their usual streams + */ + public boolean isDocPropsEncrypted() { + return !flagDocProps.isSet(getEncryptionFlags()); + } + @Override public EncryptionInfo clone() throws CloneNotSupportedException { EncryptionInfo other = (EncryptionInfo)super.clone(); diff --git a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Decryptor.java b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Decryptor.java index 1cc6b1b2f..8be9ab3fa 100644 --- a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Decryptor.java +++ b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Decryptor.java @@ -51,9 +51,9 @@ public class BinaryRC4Decryptor extends Decryptor implements Cloneable { super(stream, size, chunkSize); } - public BinaryRC4CipherInputStream(InputStream stream) + public BinaryRC4CipherInputStream(InputStream stream, int size, int initialPos) throws GeneralSecurityException { - super(stream, Integer.MAX_VALUE, chunkSize); + super(stream, size, chunkSize, initialPos); } } @@ -141,7 +141,7 @@ public class BinaryRC4Decryptor extends Decryptor implements Cloneable { @Override public InputStream getDataStream(InputStream stream, int size, int initialPos) throws IOException, GeneralSecurityException { - return new BinaryRC4CipherInputStream(stream); + return new BinaryRC4CipherInputStream(stream, size, initialPos); } diff --git a/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java b/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java index 7ec8fb6ef..25e83b95e 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java @@ -18,6 +18,7 @@ package org.apache.poi.hwpf; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -25,9 +26,29 @@ import java.io.OutputStream; import org.apache.poi.hpsf.DocumentSummaryInformation; import org.apache.poi.hpsf.SummaryInformation; -import org.apache.poi.hwpf.model.*; +import org.apache.poi.hwpf.model.BookmarksTables; +import org.apache.poi.hwpf.model.CHPBinTable; +import org.apache.poi.hwpf.model.ComplexFileTable; +import org.apache.poi.hwpf.model.DocumentProperties; +import org.apache.poi.hwpf.model.EscherRecordHolder; +import org.apache.poi.hwpf.model.FSPADocumentPart; +import org.apache.poi.hwpf.model.FSPATable; +import org.apache.poi.hwpf.model.FieldsTables; +import org.apache.poi.hwpf.model.FontTable; +import org.apache.poi.hwpf.model.ListTables; +import org.apache.poi.hwpf.model.NoteType; +import org.apache.poi.hwpf.model.NotesTables; +import org.apache.poi.hwpf.model.PAPBinTable; +import org.apache.poi.hwpf.model.PicturesTable; +import org.apache.poi.hwpf.model.RevisionMarkAuthorTable; +import org.apache.poi.hwpf.model.SavedByTable; +import org.apache.poi.hwpf.model.SectionTable; +import org.apache.poi.hwpf.model.SinglentonTextPiece; +import org.apache.poi.hwpf.model.StyleSheet; +import org.apache.poi.hwpf.model.SubdocumentType; +import org.apache.poi.hwpf.model.TextPiece; +import org.apache.poi.hwpf.model.TextPieceTable; import org.apache.poi.hwpf.model.io.HWPFFileSystem; -import org.apache.poi.hwpf.model.io.HWPFOutputStream; import org.apache.poi.hwpf.usermodel.Bookmarks; import org.apache.poi.hwpf.usermodel.BookmarksImpl; import org.apache.poi.hwpf.usermodel.Field; @@ -40,13 +61,12 @@ import org.apache.poi.hwpf.usermodel.OfficeDrawings; import org.apache.poi.hwpf.usermodel.OfficeDrawingsImpl; import org.apache.poi.hwpf.usermodel.Range; import org.apache.poi.poifs.common.POIFSConstants; +import org.apache.poi.poifs.crypt.EncryptionInfo; import org.apache.poi.poifs.filesystem.DirectoryNode; -import org.apache.poi.poifs.filesystem.DocumentEntry; import org.apache.poi.poifs.filesystem.Entry; import org.apache.poi.poifs.filesystem.EntryUtils; import org.apache.poi.poifs.filesystem.NPOIFSFileSystem; import org.apache.poi.poifs.filesystem.POIFSFileSystem; -import org.apache.poi.util.IOUtils; import org.apache.poi.util.Internal; /** @@ -59,8 +79,6 @@ public final class HWPFDocument extends HWPFDocumentCore { private static final String PROPERTY_PRESERVE_TEXT_TABLE = "org.apache.poi.hwpf.preserveTextTable"; private static final String STREAM_DATA = "Data"; - private static final String STREAM_TABLE_0 = "0Table"; - private static final String STREAM_TABLE_1 = "1Table"; /** table stream buffer*/ protected byte[] _tableStream; @@ -178,11 +196,7 @@ public final class HWPFDocument extends HWPFDocumentCore { } // use the fib to determine the name of the table stream. - String name = STREAM_TABLE_0; - if (_fib.getFibBase().isFWhichTblStm()) - { - name = STREAM_TABLE_1; - } + String name = (_fib.getFibBase().isFWhichTblStm()) ? STREAM_TABLE_1 : STREAM_TABLE_0; // Grab the table stream. if (!directory.hasEntry(name)) { @@ -190,25 +204,12 @@ public final class HWPFDocument extends HWPFDocumentCore { } // read in the table stream. - InputStream is = directory.createDocumentInputStream(name); - _tableStream = IOUtils.toByteArray(is); - is.close(); + _tableStream = getDocumentEntryBytes(name, _fib.getFibBase().getLKey(), Integer.MAX_VALUE); _fib.fillVariableFields(_mainStream, _tableStream); // read in the data stream. - InputStream dis = null; - try { - DocumentEntry dataProps = (DocumentEntry)directory.getEntry(STREAM_DATA); - dis = directory.createDocumentInputStream(STREAM_DATA); - _dataStream = IOUtils.toByteArray(dis, dataProps.getSize()); - } catch(IOException e) { - _dataStream = new byte[0]; - } finally { - if (dis != null) { - dis.close(); - } - } + _dataStream = directory.hasEntry(STREAM_DATA) ? getDocumentEntryBytes(STREAM_DATA, 0, Integer.MAX_VALUE) : new byte[0]; // Get the cp of the start of text in the main stream // The latest spec doc says this is always zero! @@ -233,8 +234,7 @@ public final class HWPFDocument extends HWPFDocumentCore { */ boolean preserveBinTables = false; try { - preserveBinTables = Boolean.parseBoolean( System - .getProperty( PROPERTY_PRESERVE_BIN_TABLES ) ); + preserveBinTables = Boolean.parseBoolean( System.getProperty( PROPERTY_PRESERVE_BIN_TABLES ) ); } catch ( Exception exc ) { // ignore; } @@ -250,8 +250,7 @@ public final class HWPFDocument extends HWPFDocumentCore { */ boolean preserveTextTable = false; try { - preserveTextTable = Boolean.parseBoolean( System - .getProperty( PROPERTY_PRESERVE_TEXT_TABLE ) ); + preserveTextTable = Boolean.parseBoolean( System.getProperty( PROPERTY_PRESERVE_TEXT_TABLE ) ); } catch ( Exception exc ) { // ignore; } @@ -612,8 +611,8 @@ public final class HWPFDocument extends HWPFDocumentCore { private void write(NPOIFSFileSystem pfs, boolean copyOtherEntries) throws IOException { // initialize our streams for writing. HWPFFileSystem docSys = new HWPFFileSystem(); - HWPFOutputStream wordDocumentStream = docSys.getStream(STREAM_WORD_DOCUMENT); - HWPFOutputStream tableStream = docSys.getStream(STREAM_TABLE_1); + ByteArrayOutputStream wordDocumentStream = docSys.getStream(STREAM_WORD_DOCUMENT); + ByteArrayOutputStream tableStream = docSys.getStream(STREAM_TABLE_1); //HWPFOutputStream dataStream = docSys.getStream("Data"); int tableOffset = 0; @@ -630,13 +629,13 @@ public final class HWPFDocument extends HWPFDocumentCore { // it after we write everything else. byte[] placeHolder = new byte[fibSize]; wordDocumentStream.write(placeHolder); - int mainOffset = wordDocumentStream.getOffset(); + int mainOffset = wordDocumentStream.size(); // write out the StyleSheet. _fib.setFcStshf(tableOffset); _ss.writeTo(tableStream); - _fib.setLcbStshf(tableStream.getOffset() - tableOffset); - tableOffset = tableStream.getOffset(); + _fib.setLcbStshf(tableStream.size() - tableOffset); + tableOffset = tableStream.size(); // get fcMin and fcMac because we will be writing the actual text with the // complex table. @@ -654,9 +653,9 @@ public final class HWPFDocument extends HWPFDocumentCore { // write out the Complex table, includes text. _fib.setFcClx(tableOffset); _cft.writeTo(wordDocumentStream, tableStream); - _fib.setLcbClx(tableStream.getOffset() - tableOffset); - tableOffset = tableStream.getOffset(); - int fcMac = wordDocumentStream.getOffset(); + _fib.setLcbClx(tableStream.size() - tableOffset); + tableOffset = tableStream.size(); + int fcMac = wordDocumentStream.size(); /* * dop (document properties record) Written immediately after the end of @@ -670,8 +669,8 @@ public final class HWPFDocument extends HWPFDocumentCore { // write out the DocumentProperties. _fib.setFcDop(tableOffset); _dop.writeTo(tableStream); - _fib.setLcbDop(tableStream.getOffset() - tableOffset); - tableOffset = tableStream.getOffset(); + _fib.setLcbDop(tableStream.size() - tableOffset); + tableOffset = tableStream.size(); /* * plcfBkmkf (table recording beginning CPs of bookmarks) Written @@ -683,7 +682,7 @@ public final class HWPFDocument extends HWPFDocumentCore { if ( _bookmarksTables != null ) { _bookmarksTables.writePlcfBkmkf( _fib, tableStream ); - tableOffset = tableStream.getOffset(); + tableOffset = tableStream.size(); } /* @@ -696,7 +695,7 @@ public final class HWPFDocument extends HWPFDocumentCore { if ( _bookmarksTables != null ) { _bookmarksTables.writePlcfBkmkl( _fib, tableStream ); - tableOffset = tableStream.getOffset(); + tableOffset = tableStream.size(); } /* @@ -710,8 +709,8 @@ public final class HWPFDocument extends HWPFDocumentCore { // write out the CHPBinTable. _fib.setFcPlcfbteChpx(tableOffset); _cbt.writeTo(wordDocumentStream, tableStream, fcMin, _cft.getTextPieceTable()); - _fib.setLcbPlcfbteChpx(tableStream.getOffset() - tableOffset); - tableOffset = tableStream.getOffset(); + _fib.setLcbPlcfbteChpx(tableStream.size() - tableOffset); + tableOffset = tableStream.size(); /* * plcfbtePapx (bin table for PAP FKPs) Written immediately after the @@ -724,8 +723,8 @@ public final class HWPFDocument extends HWPFDocumentCore { // write out the PAPBinTable. _fib.setFcPlcfbtePapx(tableOffset); _pbt.writeTo(wordDocumentStream, tableStream, _cft.getTextPieceTable()); - _fib.setLcbPlcfbtePapx(tableStream.getOffset() - tableOffset); - tableOffset = tableStream.getOffset(); + _fib.setLcbPlcfbtePapx(tableStream.size() - tableOffset); + tableOffset = tableStream.size(); /* * plcfendRef (endnote reference position table) Written immediately @@ -739,7 +738,7 @@ public final class HWPFDocument extends HWPFDocumentCore { */ _endnotesTables.writeRef( _fib, tableStream ); _endnotesTables.writeTxt( _fib, tableStream ); - tableOffset = tableStream.getOffset(); + tableOffset = tableStream.size(); /* * plcffld*** (table of field positions and statuses for annotation @@ -753,7 +752,7 @@ public final class HWPFDocument extends HWPFDocumentCore { if ( _fieldsTables != null ) { _fieldsTables.write( _fib, tableStream ); - tableOffset = tableStream.getOffset(); + tableOffset = tableStream.size(); } /* @@ -768,7 +767,7 @@ public final class HWPFDocument extends HWPFDocumentCore { */ _footnotesTables.writeRef( _fib, tableStream ); _footnotesTables.writeTxt( _fib, tableStream ); - tableOffset = tableStream.getOffset(); + tableOffset = tableStream.size(); /* * plcfsed (section table) Written immediately after the previously @@ -781,8 +780,8 @@ public final class HWPFDocument extends HWPFDocumentCore { // write out the SectionTable. _fib.setFcPlcfsed(tableOffset); _st.writeTo(wordDocumentStream, tableStream); - _fib.setLcbPlcfsed(tableStream.getOffset() - tableOffset); - tableOffset = tableStream.getOffset(); + _fib.setLcbPlcfsed(tableStream.size() - tableOffset); + tableOffset = tableStream.size(); // write out the list tables if ( _lt != null ) @@ -800,7 +799,7 @@ public final class HWPFDocument extends HWPFDocumentCore { * Specification; Page 25 of 210 */ _lt.writeListDataTo( _fib, tableStream ); - tableOffset = tableStream.getOffset(); + tableOffset = tableStream.size(); /* * plflfo (more list formats) Written immediately after the end of @@ -814,7 +813,7 @@ public final class HWPFDocument extends HWPFDocumentCore { * Specification; Page 26 of 210 */ _lt.writeListOverridesTo( _fib, tableStream ); - tableOffset = tableStream.getOffset(); + tableOffset = tableStream.size(); } /* @@ -827,7 +826,7 @@ public final class HWPFDocument extends HWPFDocumentCore { if ( _bookmarksTables != null ) { _bookmarksTables.writeSttbfBkmk( _fib, tableStream ); - tableOffset = tableStream.getOffset(); + tableOffset = tableStream.size(); } /* @@ -843,9 +842,9 @@ public final class HWPFDocument extends HWPFDocumentCore { { _fib.setFcSttbSavedBy(tableOffset); _sbt.writeTo(tableStream); - _fib.setLcbSttbSavedBy(tableStream.getOffset() - tableOffset); + _fib.setLcbSttbSavedBy(tableStream.size() - tableOffset); - tableOffset = tableStream.getOffset(); + tableOffset = tableStream.size(); } // write out the revision mark authors table. @@ -853,21 +852,21 @@ public final class HWPFDocument extends HWPFDocumentCore { { _fib.setFcSttbfRMark(tableOffset); _rmat.writeTo(tableStream); - _fib.setLcbSttbfRMark(tableStream.getOffset() - tableOffset); + _fib.setLcbSttbfRMark(tableStream.size() - tableOffset); - tableOffset = tableStream.getOffset(); + tableOffset = tableStream.size(); } // write out the FontTable. _fib.setFcSttbfffn(tableOffset); _ft.writeTo(tableStream); - _fib.setLcbSttbfffn(tableStream.getOffset() - tableOffset); - tableOffset = tableStream.getOffset(); + _fib.setLcbSttbfffn(tableStream.size() - tableOffset); + tableOffset = tableStream.size(); // set some variables in the FileInformationBlock. _fib.getFibBase().setFcMin(fcMin); _fib.getFibBase().setFcMac(fcMac); - _fib.setCbMac(wordDocumentStream.getOffset()); + _fib.setCbMac(wordDocumentStream.size()); // make sure that the table, doc and data streams use big blocks. byte[] mainBuf = wordDocumentStream.toByteArray(); diff --git a/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocumentCore.java b/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocumentCore.java index 69c1997cb..c52abc101 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocumentCore.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocumentCore.java @@ -17,13 +17,19 @@ package org.apache.poi.hwpf; +import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.PushbackInputStream; +import java.security.GeneralSecurityException; +import org.apache.poi.EncryptedDocumentException; import org.apache.poi.POIDocument; +import org.apache.poi.hpsf.PropertySet; +import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey; import org.apache.poi.hwpf.model.CHPBinTable; +import org.apache.poi.hwpf.model.FibBase; import org.apache.poi.hwpf.model.FileInformationBlock; import org.apache.poi.hwpf.model.FontTable; import org.apache.poi.hwpf.model.ListTables; @@ -34,145 +40,242 @@ import org.apache.poi.hwpf.model.TextPieceTable; import org.apache.poi.hwpf.usermodel.ObjectPoolImpl; import org.apache.poi.hwpf.usermodel.ObjectsPool; import org.apache.poi.hwpf.usermodel.Range; +import org.apache.poi.poifs.crypt.ChunkedCipherInputStream; +import org.apache.poi.poifs.crypt.Decryptor; +import org.apache.poi.poifs.crypt.EncryptionInfo; +import org.apache.poi.poifs.crypt.EncryptionMode; import org.apache.poi.poifs.filesystem.DirectoryEntry; import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.DocumentEntry; import org.apache.poi.poifs.filesystem.DocumentInputStream; import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.util.BoundedInputStream; import org.apache.poi.util.IOUtils; import org.apache.poi.util.Internal; +import org.apache.poi.util.LittleEndianByteArrayInputStream; /** * This class holds much of the core of a Word document, but * without some of the table structure information. * You generally want to work with one of - * {@link HWPFDocument} or {@link HWPFOldDocument} + * {@link HWPFDocument} or {@link HWPFOldDocument} */ -public abstract class HWPFDocumentCore extends POIDocument -{ +public abstract class HWPFDocumentCore extends POIDocument { protected static final String STREAM_OBJECT_POOL = "ObjectPool"; protected static final String STREAM_WORD_DOCUMENT = "WordDocument"; + protected static final String STREAM_TABLE_0 = "0Table"; + protected static final String STREAM_TABLE_1 = "1Table"; - /** Holds OLE2 objects */ - protected ObjectPoolImpl _objectPool; + private static final int FIB_BASE_LEN = 68; - /** The FIB */ - protected FileInformationBlock _fib; + /** Holds OLE2 objects */ + protected ObjectPoolImpl _objectPool; - /** Holds styles for this document.*/ - protected StyleSheet _ss; + /** The FIB */ + protected FileInformationBlock _fib; - /** Contains formatting properties for text*/ - protected CHPBinTable _cbt; + /** Holds styles for this document.*/ + protected StyleSheet _ss; - /** Contains formatting properties for paragraphs*/ - protected PAPBinTable _pbt; + /** Contains formatting properties for text*/ + protected CHPBinTable _cbt; - /** Contains formatting properties for sections.*/ - protected SectionTable _st; + /** Contains formatting properties for paragraphs*/ + protected PAPBinTable _pbt; - /** Holds fonts for this document.*/ - protected FontTable _ft; + /** Contains formatting properties for sections.*/ + protected SectionTable _st; - /** Hold list tables */ - protected ListTables _lt; + /** Holds fonts for this document.*/ + protected FontTable _ft; - /** main document stream buffer*/ - protected byte[] _mainStream; + /** Hold list tables */ + protected ListTables _lt; - protected HWPFDocumentCore() - { - super((DirectoryNode)null); - } + /** main document stream buffer*/ + protected byte[] _mainStream; - /** - * Takes an InputStream, verifies that it's not RTF or PDF, builds a - * POIFSFileSystem from it, and returns that. - */ - public static POIFSFileSystem verifyAndBuildPOIFS(InputStream istream) throws IOException { - // Open a PushbackInputStream, so we can peek at the first few bytes - PushbackInputStream pis = new PushbackInputStream(istream,6); - byte[] first6 = IOUtils.toByteArray(pis, 6); + private EncryptionInfo _encryptionInfo; - // Does it start with {\rtf ? If so, it's really RTF - if(first6[0] == '{' && first6[1] == '\\' && first6[2] == 'r' - && first6[3] == 't' && first6[4] == 'f') { - throw new IllegalArgumentException("The document is really a RTF file"); - } else if(first6[0] == '%' && first6[1] == 'P' && first6[2] == 'D' && first6[3] == 'F' ) { - throw new IllegalArgumentException("The document is really a PDF file"); - } + protected HWPFDocumentCore() { + super((DirectoryNode)null); + } - // OK, so it's neither RTF nor PDF - // Open a POIFSFileSystem on the (pushed back) stream - pis.unread(first6); - return new POIFSFileSystem(pis); - } + /** + * Takes an InputStream, verifies that it's not RTF or PDF, builds a + * POIFSFileSystem from it, and returns that. + */ + public static POIFSFileSystem verifyAndBuildPOIFS(InputStream istream) throws IOException { + // Open a PushbackInputStream, so we can peek at the first few bytes + PushbackInputStream pis = new PushbackInputStream(istream,6); + byte[] first6 = IOUtils.toByteArray(pis, 6); - /** - * This constructor loads a Word document from an InputStream. - * - * @param istream The InputStream that contains the Word document. - * @throws IOException If there is an unexpected IOException from the passed - * in InputStream. - */ - public HWPFDocumentCore(InputStream istream) throws IOException - { - //do Ole stuff - this( verifyAndBuildPOIFS(istream) ); - } + // Does it start with {\rtf ? If so, it's really RTF + if(first6[0] == '{' && first6[1] == '\\' && first6[2] == 'r' + && first6[3] == 't' && first6[4] == 'f') { + throw new IllegalArgumentException("The document is really a RTF file"); + } else if(first6[0] == '%' && first6[1] == 'P' && first6[2] == 'D' && first6[3] == 'F' ) { + throw new IllegalArgumentException("The document is really a PDF file"); + } - /** - * This constructor loads a Word document from a POIFSFileSystem - * - * @param pfilesystem The POIFSFileSystem that contains the Word document. - * @throws IOException If there is an unexpected IOException from the passed - * in POIFSFileSystem. - */ - public HWPFDocumentCore(POIFSFileSystem pfilesystem) throws IOException - { - this(pfilesystem.getRoot()); - } + // OK, so it's neither RTF nor PDF + // Open a POIFSFileSystem on the (pushed back) stream + pis.unread(first6); + return new POIFSFileSystem(pis); + } - /** - * This constructor loads a Word document from a specific point - * in a POIFSFileSystem, probably not the default. - * Used typically to open embeded documents. - * - * @param directory The DirectoryNode that contains the Word document. - * @throws IOException If there is an unexpected IOException from the passed - * in POIFSFileSystem. - */ - public HWPFDocumentCore(DirectoryNode directory) throws IOException { - // Sort out the hpsf properties - super(directory); + /** + * This constructor loads a Word document from an InputStream. + * + * @param istream The InputStream that contains the Word document. + * @throws IOException If there is an unexpected IOException from the passed + * in InputStream. + */ + public HWPFDocumentCore(InputStream istream) throws IOException { + //do Ole stuff + this( verifyAndBuildPOIFS(istream) ); + } - // read in the main stream. - DocumentEntry documentProps = (DocumentEntry)directory.getEntry("WordDocument"); - DocumentInputStream dis = null; - try { - dis = directory.createDocumentInputStream(STREAM_WORD_DOCUMENT); - _mainStream = IOUtils.toByteArray(dis, documentProps.getSize()); - } finally { - if (dis != null) { - dis.close(); + /** + * This constructor loads a Word document from a POIFSFileSystem + * + * @param pfilesystem The POIFSFileSystem that contains the Word document. + * @throws IOException If there is an unexpected IOException from the passed + * in POIFSFileSystem. + */ + public HWPFDocumentCore(POIFSFileSystem pfilesystem) throws IOException { + this(pfilesystem.getRoot()); + } + + /** + * This constructor loads a Word document from a specific point + * in a POIFSFileSystem, probably not the default. + * Used typically to open embeded documents. + * + * @param directory The DirectoryNode that contains the Word document. + * @throws IOException If there is an unexpected IOException from the passed + * in POIFSFileSystem. + */ + public HWPFDocumentCore(DirectoryNode directory) throws IOException { + // Sort out the hpsf properties + super(directory); + + // read in the main stream. + _mainStream = getDocumentEntryBytes(STREAM_WORD_DOCUMENT, FIB_BASE_LEN, Integer.MAX_VALUE); + _fib = new FileInformationBlock(_mainStream); + + DirectoryEntry objectPoolEntry = null; + if (directory.hasEntry(STREAM_OBJECT_POOL)) { + objectPoolEntry = (DirectoryEntry) directory.getEntry(STREAM_OBJECT_POOL); + } + _objectPool = new ObjectPoolImpl(objectPoolEntry); + } + + /** + * For a given named property entry, either return it or null if + * if it wasn't found + * + * @param setName The property to read + * @return The value of the given property or null if it wasn't found. + */ + @Override + protected PropertySet getPropertySet(String setName) { + EncryptionInfo ei; + try { + ei = getEncryptionInfo(); + } catch (IOException e) { + throw new RuntimeException(e); + } + return (ei == null) + ? super.getPropertySet(setName) + : super.getPropertySet(setName, ei); + } + + protected EncryptionInfo getEncryptionInfo() throws IOException { + if (_encryptionInfo != null) { + return _encryptionInfo; + } + + // Create our FIB, and check for the doc being encrypted + byte[] fibBaseBytes = (_mainStream != null) ? _mainStream : getDocumentEntryBytes(STREAM_WORD_DOCUMENT, -1, FIB_BASE_LEN); + FibBase fibBase = new FibBase( fibBaseBytes, 0 ); + if (!fibBase.isFEncrypted()) { + return null; + } + + String tableStrmName = fibBase.isFWhichTblStm() ? STREAM_TABLE_1 : STREAM_TABLE_0; + byte[] tableStream = getDocumentEntryBytes(tableStrmName, -1, fibBase.getLKey()); + LittleEndianByteArrayInputStream leis = new LittleEndianByteArrayInputStream(tableStream); + EncryptionMode em = fibBase.isFObfuscated() ? EncryptionMode.xor : null; + EncryptionInfo ei = new EncryptionInfo(leis, em); + Decryptor dec = ei.getDecryptor(); + dec.setChunkSize(512); + try { + String pass = Biff8EncryptionKey.getCurrentUserPassword(); + if (pass == null) { + pass = Decryptor.DEFAULT_PASSWORD; + } + if (!dec.verifyPassword(pass)) { + throw new EncryptedDocumentException("document is encrypted, password is invalid - use Biff8EncryptionKey.setCurrentUserPasswort() to set password before opening"); + } + } catch (GeneralSecurityException e) { + throw new IOException(e.getMessage(), e); + } + _encryptionInfo = ei; + return ei; + } + + /** + * Reads OLE Stream into byte array - if an {@link EncryptionInfo} is available, + * decrypt the bytes starting at encryptionOffset. If encryptionOffset = -1, then do not try + * to decrypt the bytes + * + * @param name the name of the stream + * @param encryptionOffset the offset from which to start decrypting, use {@code -1} for no decryption + * @param len length of the bytes to be read, use {@link Integer#MAX_VALUE} for all bytes + * @return the read bytes + * @throws IOException if the stream can't be found + */ + protected byte[] getDocumentEntryBytes(String name, int encryptionOffset, int len) throws IOException { + DirectoryNode dir = getDirectory(); + DocumentEntry documentProps = (DocumentEntry)dir.getEntry(name); + DocumentInputStream dis = dir.createDocumentInputStream(documentProps); + EncryptionInfo ei = (encryptionOffset > -1) ? getEncryptionInfo() : null; + int streamSize = documentProps.getSize(); + ByteArrayOutputStream bos = new ByteArrayOutputStream(Math.min(streamSize,len)); + + InputStream is = dis; + try { + if (ei != null) { + try { + Decryptor dec = ei.getDecryptor(); + is = dec.getDataStream(dis, streamSize, 0); + if (encryptionOffset > 0) { + ChunkedCipherInputStream cis = (ChunkedCipherInputStream)is; + byte plain[] = new byte[encryptionOffset]; + cis.readPlain(plain, 0, encryptionOffset); + bos.write(plain); + } + } catch (GeneralSecurityException e) { + throw new IOException(e.getMessage(), e); + } + } + // This simplifies a few combinations, so we actually always try to copy len bytes + // regardless if encryptionOffset is greater than 0 + if (len < Integer.MAX_VALUE) { + is = new BoundedInputStream(is, len); + } + IOUtils.copy(is, bos); + return bos.toByteArray(); + } finally { + IOUtils.closeQuietly(is); + IOUtils.closeQuietly(dis); } } - // Create our FIB, and check for the doc being encrypted - _fib = new FileInformationBlock(_mainStream); - DirectoryEntry objectPoolEntry; - try { - objectPoolEntry = (DirectoryEntry) directory - .getEntry(STREAM_OBJECT_POOL); - } catch (FileNotFoundException exc) { - objectPoolEntry = null; - } - _objectPool = new ObjectPoolImpl(objectPoolEntry); - } - - /** + /** * Returns the range which covers the whole of the document, but excludes * any headers and footers. */ @@ -198,43 +301,35 @@ public abstract class HWPFDocumentCore extends POIDocument @Internal public abstract StringBuilder getText(); - public CHPBinTable getCharacterTable() - { - return _cbt; - } + public CHPBinTable getCharacterTable() { + return _cbt; + } - public PAPBinTable getParagraphTable() - { - return _pbt; - } + public PAPBinTable getParagraphTable() { + return _pbt; + } - public SectionTable getSectionTable() - { - return _st; - } + public SectionTable getSectionTable() { + return _st; + } - public StyleSheet getStyleSheet() - { - return _ss; - } + public StyleSheet getStyleSheet() { + return _ss; + } - public ListTables getListTables() - { - return _lt; - } + public ListTables getListTables() { + return _lt; + } - public FontTable getFontTable() - { - return _ft; - } + public FontTable getFontTable() { + return _ft; + } - public FileInformationBlock getFileInformationBlock() - { - return _fib; - } + public FileInformationBlock getFileInformationBlock() { + return _fib; + } - public ObjectsPool getObjectsPool() - { + public ObjectsPool getObjectsPool() { return _objectPool; } @@ -244,4 +339,4 @@ public abstract class HWPFDocumentCore extends POIDocument public byte[] getMainStream() { return _mainStream; } -} +} \ No newline at end of file diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/HWPFTestEncryption.java b/src/scratchpad/testcases/org/apache/poi/hwpf/HWPFTestEncryption.java new file mode 100644 index 000000000..875fb9ec7 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hwpf/HWPFTestEncryption.java @@ -0,0 +1,69 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hwpf; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; + +import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey; +import org.apache.poi.hwpf.extractor.WordExtractor; +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class HWPFTestEncryption { + @AfterClass + public static void clearPass() { + Biff8EncryptionKey.setCurrentUserPassword(null); + } + + @Parameter(value = 0) + public String file; + + @Parameter(value = 1) + public String password; + + @Parameter(value = 2) + public String expected; + + @Parameters(name="{0}") + public static Collection data() { + return Arrays.asList( + new String[]{ "password_tika_binaryrc4.doc", "tika", "This is an encrypted Word 2007 File." }, + new String[]{ "password_password_cryptoapi.doc", "password", "This is a test" } + ); + } + + @Test + public void extract() throws IOException { + Biff8EncryptionKey.setCurrentUserPassword(password); + HWPFDocument docD = HWPFTestDataSamples.openSampleFile(file); + WordExtractor we = new WordExtractor(docD); + String actual = we.getText().trim(); + assertEquals(expected, actual); + we.close(); + docD.close(); + } +} diff --git a/test-data/document/password_password_cryptoapi.doc b/test-data/document/password_password_cryptoapi.doc new file mode 100644 index 0000000000000000000000000000000000000000..7ef0128582e5d8fbf3291aad4a51ada8f97c223b GIT binary patch literal 27136 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 literal 0 HcmV?d00001 diff --git a/test-data/document/password_tika_binaryrc4.doc b/test-data/document/password_tika_binaryrc4.doc new file mode 100644 index 0000000000000000000000000000000000000000..b407783d214c148432ef9a2565c570bd72cbf72d GIT binary patch literal 22016 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 literal 0 HcmV?d00001