]> source.dussan.org Git - poi.git/commitdiff
#61162 - En-/decryption support for HWPF
authorAndreas Beeker <kiwiwings@apache.org>
Wed, 14 Jun 2017 17:21:50 +0000 (17:21 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Wed, 14 Jun 2017 17:21:50 +0000 (17:21 +0000)
- encryption support for HWPF
- refactor/unify EncryptionInfo handling in H**F classes
- remove Iterable interface from WorkbookRecordList - use getRecords() instead, to prevent modifications over the Iterator

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1798722 13f79535-47bb-0310-9956-ffa450edef68

16 files changed:
build.xml
src/java/org/apache/poi/POIDocument.java
src/java/org/apache/poi/POIOLE2TextExtractor.java
src/java/org/apache/poi/hssf/model/InternalWorkbook.java
src/java/org/apache/poi/hssf/model/LinkTable.java
src/java/org/apache/poi/hssf/model/WorkbookRecordList.java
src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDecryptor.java
src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptor.java
src/ooxml/testcases/org/apache/poi/poifs/crypt/TestHxxFEncryption.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowEncrypted.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowImpl.java
src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java
src/scratchpad/src/org/apache/poi/hwpf/HWPFDocumentCore.java
src/scratchpad/src/org/apache/poi/hwpf/model/io/HWPFFileSystem.java
src/scratchpad/testcases/org/apache/poi/hwpf/HWPFTestEncryption.java [deleted file]

index 4106a0fa0b23c65f14cb89da8eb503385b8247c2..52ed07b3fe5d7165099bae13a39a37da59365959 100644 (file)
--- a/build.xml
+++ b/build.xml
@@ -463,6 +463,7 @@ under the License.
         <exclude name="**/TestExtractorFactory.java"/>
         <exclude name="**/OutlookTextExtactor.java"/>
         <exclude name="**/TestEmbedOLEPackage.java"/>
+       <exclude name="**/TestHxxFEncryption.java"/>
     </patternset>
 
     <!-- Prints POI's Ant usage help -->
index 774507722a6c17f99c91c9a2d265069e031e2b64..5c2c2015125fcb6a2c60fccc0e160f17d8b258af 100644 (file)
@@ -24,6 +24,7 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.security.GeneralSecurityException;
 import java.util.List;
 
 import org.apache.poi.hpsf.DocumentSummaryInformation;
@@ -31,12 +32,15 @@ import org.apache.poi.hpsf.PropertySet;
 import org.apache.poi.hpsf.PropertySetFactory;
 import org.apache.poi.hpsf.SummaryInformation;
 import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.poifs.crypt.Encryptor;
 import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIDecryptor;
+import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptor;
 import org.apache.poi.poifs.filesystem.DirectoryNode;
 import org.apache.poi.poifs.filesystem.DocumentInputStream;
 import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
 import org.apache.poi.poifs.filesystem.OPOIFSFileSystem;
 import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.Internal;
 import org.apache.poi.util.POILogFactory;
 import org.apache.poi.util.POILogger;
@@ -60,8 +64,6 @@ public abstract class POIDocument implements Closeable {
     /* Have the property streams been read yet? (Only done on-demand) */
     private boolean initialized;
 
-    private static final String[] encryptedStreamNames = { "EncryptedSummary" };
-    
     /**
      * Constructs a POIDocument with the given directory node.
      *
@@ -103,7 +105,9 @@ public abstract class POIDocument implements Closeable {
      *      if it could not be read for this document.
      */
     public DocumentSummaryInformation getDocumentSummaryInformation() {
-        if(!initialized) readProperties();
+        if(!initialized) {
+            readProperties();
+        }
         return dsInf;
     }
 
@@ -114,7 +118,9 @@ public abstract class POIDocument implements Closeable {
      *      if it could not be read for this document.
      */
     public SummaryInformation getSummaryInformation() {
-        if(!initialized) readProperties();
+        if(!initialized) {
+            readProperties();
+        }
         return sInf;
     }
        
@@ -128,7 +134,9 @@ public abstract class POIDocument implements Closeable {
      *  then nothing will happen.
      */
     public void createInformationProperties() {
-        if (!initialized) readProperties();
+        if (!initialized) {
+            readProperties();
+        }
         if (sInf == null) {
             sInf = PropertySetFactory.newSummaryInformation();
         }
@@ -144,32 +152,40 @@ public abstract class POIDocument implements Closeable {
      *  it will remain null;
      */
     protected void readProperties() {
-        PropertySet ps;
-
-        // DocumentSummaryInformation
-        ps = getPropertySet(DocumentSummaryInformation.DEFAULT_STREAM_NAME);
-        if (ps instanceof DocumentSummaryInformation) {
-            dsInf = (DocumentSummaryInformation)ps;
-        } else if (ps != null) {
-            logger.log(POILogger.WARN, "DocumentSummaryInformation property set came back with wrong class - ", ps.getClass());
-        } else {
-            logger.log(POILogger.WARN, "DocumentSummaryInformation property set came back as null");
+        if (initialized) {
+            return;
         }
-
-        // SummaryInformation
-        ps = getPropertySet(SummaryInformation.DEFAULT_STREAM_NAME);
-        if (ps instanceof SummaryInformation) {
-            sInf = (SummaryInformation)ps;
-        } else if (ps != null) {
-            logger.log(POILogger.WARN, "SummaryInformation property set came back with wrong class - ", ps.getClass());
-        } else {
-            logger.log(POILogger.WARN, "SummaryInformation property set came back as null");
+        DocumentSummaryInformation dsi = readPropertySet(DocumentSummaryInformation.class, DocumentSummaryInformation.DEFAULT_STREAM_NAME);
+        if (dsi != null) {
+            dsInf = dsi;
+        }
+        SummaryInformation si = readPropertySet(SummaryInformation.class, SummaryInformation.DEFAULT_STREAM_NAME);
+        if (si != null) {
+            sInf = si;
         }
 
         // Mark the fact that we've now loaded up the properties
         initialized = true;
     }
 
+    @SuppressWarnings("unchecked")
+    private <T> T readPropertySet(Class<T> clazz, String name) {
+        String localName = clazz.getName().substring(clazz.getName().lastIndexOf('.')+1);
+        try {
+            PropertySet ps = getPropertySet(name);
+            if (clazz.isInstance(ps)) {
+                return (T)ps;
+            } else if (ps != null) {
+                logger.log(POILogger.WARN, localName+" property set came back with wrong class - "+ps.getClass().getName());
+            } else {
+                logger.log(POILogger.WARN, localName+" property set came back as null");
+            }
+        } catch (IOException e) {
+            logger.log(POILogger.ERROR, "can't retrieve property set", e);
+        }
+        return null;
+    }
+    
     /** 
      * For a given named property entry, either return it or null if
      *  if it wasn't found
@@ -177,8 +193,8 @@ public abstract class POIDocument implements Closeable {
      *  @param setName The property to read
      *  @return The value of the given property or null if it wasn't found.
      */
-    protected PropertySet getPropertySet(String setName) {
-        return getPropertySet(setName, null);
+    protected PropertySet getPropertySet(String setName) throws IOException {
+        return getPropertySet(setName, getEncryptionInfo());
     }
     
     /** 
@@ -189,7 +205,7 @@ public abstract class POIDocument implements Closeable {
      *  @param encryptionInfo the encryption descriptor in case of cryptoAPI encryption
      *  @return The value of the given property or null if it wasn't found.
      */
-    protected PropertySet getPropertySet(String setName, EncryptionInfo encryptionInfo) {
+    protected PropertySet getPropertySet(String setName, EncryptionInfo encryptionInfo) throws IOException {
         DirectoryNode dirNode = directory;
         
         NPOIFSFileSystem encPoifs = null;
@@ -197,14 +213,9 @@ public abstract class POIDocument implements Closeable {
         try {
             if (encryptionInfo != null && encryptionInfo.isDocPropsEncrypted()) {
                 step = "getting encrypted";
-                String encryptedStream = null;
-                for (String s : encryptedStreamNames) {
-                    if (dirNode.hasEntry(s)) {
-                        encryptedStream = s;
-                    }
-                }
-                if (encryptedStream == null) {
-                    throw new EncryptedDocumentException("can't find matching encrypted property stream");
+                String encryptedStream = getEncryptedPropertyStreamName();
+                if (!dirNode.hasEntry(encryptedStream)) {
+                    throw new EncryptedDocumentException("can't find encrypted property stream '"+encryptedStream+"'");
                 }
                 CryptoAPIDecryptor dec = (CryptoAPIDecryptor)encryptionInfo.getDecryptor();
                 encPoifs = dec.getSummaryEntries(dirNode, encryptedStream);
@@ -226,17 +237,12 @@ public abstract class POIDocument implements Closeable {
             } finally {
                 dis.close();
             }
+        } catch (IOException e) {
+            throw e;
         } catch (Exception e) {
-            logger.log(POILogger.WARN, "Error "+step+" property set with name " + setName, e);
-            return null;
+            throw new IOException("Error "+step+" property set with name " + setName, e);
         } finally {
-            if (encPoifs != null) {
-                try {
-                    encPoifs.close();
-                } catch(IOException e) {
-                    logger.log(POILogger.WARN, "Error closing encrypted property poifs", e);
-                }
-            }
+            IOUtils.closeQuietly(encPoifs);
         }
     }
     
@@ -271,20 +277,48 @@ public abstract class POIDocument implements Closeable {
      *      {@link NPOIFSFileSystem} occurs
      */
     protected void writeProperties(NPOIFSFileSystem outFS, List<String> writtenEntries) throws IOException {
+        EncryptionInfo ei = getEncryptionInfo();
+        final boolean encryptProps = (ei != null && ei.isDocPropsEncrypted());
+        NPOIFSFileSystem fs = (encryptProps) ? new NPOIFSFileSystem() : outFS;
+        
         SummaryInformation si = getSummaryInformation();
         if (si != null) {
-            writePropertySet(SummaryInformation.DEFAULT_STREAM_NAME, si, outFS);
+            writePropertySet(SummaryInformation.DEFAULT_STREAM_NAME, si, fs);
             if(writtenEntries != null) {
                 writtenEntries.add(SummaryInformation.DEFAULT_STREAM_NAME);
             }
         }
         DocumentSummaryInformation dsi = getDocumentSummaryInformation();
         if (dsi != null) {
-            writePropertySet(DocumentSummaryInformation.DEFAULT_STREAM_NAME, dsi, outFS);
+            writePropertySet(DocumentSummaryInformation.DEFAULT_STREAM_NAME, dsi, fs);
             if(writtenEntries != null) {
                 writtenEntries.add(DocumentSummaryInformation.DEFAULT_STREAM_NAME);
             }
         }
+
+        if (!encryptProps) {
+            return;
+        }
+
+        // create empty document summary
+        dsi = PropertySetFactory.newDocumentSummaryInformation();
+        writePropertySet(DocumentSummaryInformation.DEFAULT_STREAM_NAME, dsi, outFS);
+        // remove summary, if previously available
+        if (outFS.getRoot().hasEntry(SummaryInformation.DEFAULT_STREAM_NAME)) {
+            outFS.getRoot().getEntry(SummaryInformation.DEFAULT_STREAM_NAME).delete();
+        }
+        Encryptor encGen = ei.getEncryptor();
+        if (!(encGen instanceof CryptoAPIEncryptor)) {
+            throw new EncryptedDocumentException("Using "+ei.getEncryptionMode()+" encryption. Only CryptoAPI encryption supports encrypted property sets!");
+        }
+        CryptoAPIEncryptor enc = (CryptoAPIEncryptor)encGen;
+        try {
+            enc.setSummaryEntries(outFS.getRoot(), getEncryptedPropertyStreamName(), fs);
+        } catch (GeneralSecurityException e) {
+            throw new IOException(e);
+        } finally {
+            fs.close();
+        }
     }
        
     /**
@@ -443,4 +477,18 @@ public abstract class POIDocument implements Closeable {
         directory = newDirectory;
         return dn;
     }
+
+    /**
+     * @return the stream name of the property set collection, if the document is encrypted
+     */
+    protected String getEncryptedPropertyStreamName() {
+        return "encryption";
+    }
+
+    /**
+     * @return the encryption info if the document is encrypted, otherwise {@code null}
+     */
+    public EncryptionInfo getEncryptionInfo() throws IOException {
+        return null;
+    }
 }
index 5884a9054ec92c7a46dd4b3266709cd173d89346..05c7781057d87e3f5f8be1d3792033b742b62185 100644 (file)
@@ -27,7 +27,7 @@ import org.apache.poi.poifs.filesystem.DirectoryEntry;
  * You will typically find the implementation of
  *  a given format's text extractor under
  *  org.apache.poi.[format].extractor .
- *  
+ *
  * @see org.apache.poi.hssf.extractor.ExcelExtractor
  * @see org.apache.poi.hslf.extractor.PowerPointExtractor
  * @see org.apache.poi.hdgf.extractor.VisioTextExtractor
@@ -39,12 +39,12 @@ public abstract class POIOLE2TextExtractor extends POITextExtractor {
 
        /**
         * Creates a new text extractor for the given document
-        * 
+        *
         * @param document The POIDocument to use in this extractor.
         */
        public POIOLE2TextExtractor(POIDocument document) {
                this.document = document;
-               
+
                // Ensure any underlying resources, such as open files,
                //  will get cleaned up if the user calls #close()
                setFilesystem(document);
@@ -54,17 +54,17 @@ public abstract class POIOLE2TextExtractor extends POITextExtractor {
         * Creates a new text extractor, using the same
         *  document as another text extractor. Normally
         *  only used by properties extractors.
-        * 
+        *
         * @param otherExtractor the extractor which document to be used
         */
        protected POIOLE2TextExtractor(POIOLE2TextExtractor otherExtractor) {
                this.document = otherExtractor.document;
        }
-       
+
        /**
         * Returns the document information metadata for the document
-        * 
-     * @return The Document Summary Information or null 
+        *
+     * @return The Document Summary Information or null
      *      if it could not be read for this document.
         */
        public DocumentSummaryInformation getDocSummaryInformation() {
@@ -72,7 +72,7 @@ public abstract class POIOLE2TextExtractor extends POITextExtractor {
        }
        /**
         * Returns the summary information metadata for the document.
-        * 
+        *
      * @return The Summary information for the document or null
      *      if it could not be read for this document.
         */
@@ -83,7 +83,7 @@ public abstract class POIOLE2TextExtractor extends POITextExtractor {
        /**
         * Returns an HPSF powered text extractor for the
         *  document properties metadata, such as title and author.
-        *  
+        *
         * @return an instance of POIExtractor that can extract meta-data.
         */
        @Override
@@ -96,8 +96,16 @@ public abstract class POIOLE2TextExtractor extends POITextExtractor {
         *
         * @return the DirectoryEntry that is associated with the POIDocument of this extractor.
         */
-    public DirectoryEntry getRoot()
-    {
+    public DirectoryEntry getRoot() {
         return document.getDirectory();
     }
-}
+
+    /**
+     * Return the underlying POIDocument
+     *
+     * @return the underlying POIDocument
+     */
+    public POIDocument getDocument() {
+        return document;
+    }
+}
\ No newline at end of file
index 7bddcdd6705eb82455384ea6d97893deec5907a5..1538ca5835e8f7966db4f2aad2e912fec1b04730 100644 (file)
@@ -20,16 +20,12 @@ package org.apache.poi.hssf.model;
 import static org.apache.poi.util.POILogger.DEBUG;
 
 import java.security.AccessControlException;
-import java.security.GeneralSecurityException;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 
-import javax.crypto.SecretKey;
-
-import org.apache.poi.EncryptedDocumentException;
 import org.apache.poi.ddf.EscherBSERecord;
 import org.apache.poi.ddf.EscherBoolProperty;
 import org.apache.poi.ddf.EscherContainerRecord;
@@ -57,7 +53,6 @@ import org.apache.poi.hssf.record.EscherAggregate;
 import org.apache.poi.hssf.record.ExtSSTRecord;
 import org.apache.poi.hssf.record.ExtendedFormatRecord;
 import org.apache.poi.hssf.record.ExternSheetRecord;
-import org.apache.poi.hssf.record.FilePassRecord;
 import org.apache.poi.hssf.record.FileSharingRecord;
 import org.apache.poi.hssf.record.FnGroupCountRecord;
 import org.apache.poi.hssf.record.FontRecord;
@@ -88,13 +83,8 @@ import org.apache.poi.hssf.record.WindowProtectRecord;
 import org.apache.poi.hssf.record.WriteAccessRecord;
 import org.apache.poi.hssf.record.WriteProtectRecord;
 import org.apache.poi.hssf.record.common.UnicodeString;
-import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
 import org.apache.poi.hssf.util.HSSFColor.HSSFColorPredefined;
 import org.apache.poi.poifs.crypt.CryptoFunctions;
-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.crypt.Encryptor;
 import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalName;
 import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
 import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheetRange;
@@ -1048,7 +1038,7 @@ public final class InternalWorkbook {
         SSTRecord lSST = null;
         int sstPos = 0;
         boolean wroteBoundSheets = false;
-        for ( Record record : records ) {
+        for ( Record record : records.getRecords() ) {
             int len = 0;
             if (record instanceof SSTRecord) {
                 lSST = (SSTRecord)record;
@@ -1080,8 +1070,6 @@ public final class InternalWorkbook {
      * Include in it ant code that modifies the workbook record stream and affects its size.
      */
     public void preSerialize(){
-        updateEncryptionRecord();
-
         // Ensure we have enough tab IDs
         // Can be a few short if new sheets were added
         if(records.getTabpos() > 0) {
@@ -1092,45 +1080,11 @@ public final class InternalWorkbook {
         }
     }
 
-    private void updateEncryptionRecord() {
-        FilePassRecord fpr = (FilePassRecord)findFirstRecordBySid(FilePassRecord.sid);
-
-        String password = Biff8EncryptionKey.getCurrentUserPassword();
-        if (password == null) {
-            if (fpr != null) {
-                // need to remove password data
-                records.remove(fpr);
-            }
-        } else {
-            // create password record
-            if (fpr == null) {
-                fpr = new FilePassRecord(EncryptionMode.binaryRC4);
-                records.add(1, fpr);
-            }
-
-            // check if the password has been changed
-            EncryptionInfo ei = fpr.getEncryptionInfo();
-            byte encVer[] = ei.getVerifier().getEncryptedVerifier();
-            try {
-                Decryptor dec = ei.getDecryptor();
-                Encryptor enc = ei.getEncryptor();
-                if (encVer == null || !dec.verifyPassword(password)) {
-                    enc.confirmPassword(password);
-                } else {
-                    SecretKey sk = dec.getSecretKey();
-                    ei.getEncryptor().setSecretKey(sk);
-                }
-            } catch (GeneralSecurityException e) {
-                throw new EncryptedDocumentException("can't validate/update encryption setting", e);
-            }
-        }
-    }
-
     public int getSize() {
         int retval = 0;
 
         SSTRecord lSST = null;
-        for ( Record record : records ) {
+        for ( Record record : records.getRecords() ) {
             if (record instanceof SSTRecord) {
                 lSST = (SSTRecord)record;
             }
@@ -1803,7 +1757,7 @@ public final class InternalWorkbook {
      * @return the matching record or {@code null} if it wasn't found
      */
     public Record findFirstRecordBySid(short sid) {
-        for (Record record : records) {
+        for (Record record : records.getRecords() ) {
             if (record.getSid() == sid) {
                 return record;
             }
@@ -1818,7 +1772,7 @@ public final class InternalWorkbook {
      */
     public int findFirstRecordLocBySid(short sid) {
         int index = 0;
-        for (Record record : records) {
+        for (Record record : records.getRecords() ) {
             if (record.getSid() == sid) {
                 return index;
             }
@@ -1837,7 +1791,7 @@ public final class InternalWorkbook {
      */
     public Record findNextRecordBySid(short sid, int pos) {
         int matches = 0;
-        for (Record record : records) {
+        for (Record record : records.getRecords() ) {
             if (record.getSid() == sid && matches++ == pos) {
                 return record;
             }
@@ -1901,7 +1855,7 @@ public final class InternalWorkbook {
         }
 
         // Need to find a DrawingGroupRecord that contains a EscherDggRecord
-        for(Record r : records) {
+        for(Record r : records.getRecords() ) {
             if (!(r instanceof DrawingGroupRecord)) {
                 continue;
             }
@@ -2301,4 +2255,12 @@ public final class InternalWorkbook {
     public boolean changeExternalReference(String oldUrl, String newUrl) {
        return linkTable.changeExternalReference(oldUrl, newUrl);
     }
+
+    /**
+     * Only for internal calls - code based on this is not supported ...
+     */
+    @Internal
+    public WorkbookRecordList getWorkbookRecordList() {
+        return records;
+    }
 }
\ No newline at end of file
index f6f75692cec1f531a2b272cbb14e65830d90e28d..cc40258382988c7b2282aa1ad94301f08224f99e 100644 (file)
@@ -522,9 +522,7 @@ final class LinkTable {
         */
        private int findFirstRecordLocBySid(short sid) {
                int index = 0;
-               for (Iterator<Record> iterator = _workbookRecordList.iterator(); iterator.hasNext(); ) {
-                       Record record = iterator.next();
-
+               for (Record record : _workbookRecordList.getRecords()) {
                        if (record.getSid() == sid) {
                                return index;
                        }
@@ -639,11 +637,11 @@ final class LinkTable {
         int supLinkIndex = 0;
         // find the posistion of the Add-In SupBookRecord in the workbook stream,
         // the created ExternalNameRecord will be appended to it
-        for (Iterator<Record> iterator = _workbookRecordList.iterator(); iterator.hasNext(); supLinkIndex++) {
-            Record record = iterator.next();
-            if (record instanceof SupBookRecord) {
-                if (((SupBookRecord) record).isAddInFunctions()) break;
+        for (Record record : _workbookRecordList.getRecords()) {
+            if (record instanceof SupBookRecord && ((SupBookRecord) record).isAddInFunctions()) {
+                break;
             }
+            supLinkIndex++;
         }
         int numberOfNames = extBlock.getNumberOfNames();
         // a new name is inserted in the end of the SupBookRecord, after the last name
index e02f8ba84a54f0ee8d5a9a1f000e83232e577c40..b29d7268e9f1589a61bed42a0e1addcd2c721c9b 100644 (file)
 package org.apache.poi.hssf.model;
 
 import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
 
 import org.apache.poi.hssf.record.Record;
 
-public final class WorkbookRecordList implements Iterable<Record> {
+public final class WorkbookRecordList {
     private List<Record> records = new ArrayList<Record>();
 
-       private int  protpos     = 0;   // holds the position of the protect record.
-       private int  bspos         = 0;   // holds the position of the last bound sheet.
-       private int  tabpos       = 0;   // holds the position of the tabid record
-       private int  fontpos     = 0;   // hold the position of the last font record
-       private int  xfpos         = 0;   // hold the position of the last extended font record
-       private int  backuppos   = 0;   // holds the position of the backup record.
-       private int  namepos     = 0;   // holds the position of last name record
-       private int  supbookpos  = 0;   // holds the position of sup book
-       private int  externsheetPos = 0;// holds the position of the extern sheet
-       private int  palettepos  = -1;   // hold the position of the palette, if applicable
+    /** holds the position of the protect record */
+       private int protpos            = 0;
+    /** holds the position of the last bound sheet */
+       private int bspos              = 0;
+    /** holds the position of the tabid record */
+       private int tabpos             = 0;
+    /** hold the position of the last font record */
+       private int fontpos            = 0;
+    /** hold the position of the last extended font record */
+       private int xfpos              = 0;
+    /** holds the position of the backup record */
+       private int backuppos      = 0;
+    /** holds the position of last name record */
+       private int namepos            = 0;
+       /** holds the position of sup book */
+       private int supbookpos     = 0;
+       /** holds the position of the extern sheet */
+       private int externsheetPos = 0;
+       /** hold the position of the palette, if applicable */
+       private int palettepos     = -1;
 
 
        public void setRecords(List<Record> records) {
-               this.records = records;
+           this.records = records;
        }
 
        public int size() {
@@ -52,26 +61,13 @@ public final class WorkbookRecordList implements Iterable<Record> {
 
        public void add(int pos, Record r) {
                records.add(pos, r);
-               if (getProtpos() >= pos) setProtpos( protpos + 1 );
-               if (getBspos() >= pos) setBspos( bspos + 1 );
-               if (getTabpos() >= pos) setTabpos( tabpos + 1 );
-               if (getFontpos() >= pos) setFontpos( fontpos + 1 );
-               if (getXfpos() >= pos) setXfpos( xfpos + 1 );
-               if (getBackuppos() >= pos) setBackuppos( backuppos + 1 );
-               if (getNamepos() >= pos) setNamepos(namepos+1);
-               if (getSupbookpos() >= pos) setSupbookpos(supbookpos+1);
-               if ((getPalettepos() != -1) && (getPalettepos() >= pos)) setPalettepos( palettepos + 1 );
-               if (getExternsheetPos() >= pos) setExternsheetPos(getExternsheetPos() + 1);
+               updateRecordPos(pos, true);
        }
 
        public List<Record> getRecords() {
                return records;
        }
 
-       public Iterator<Record> iterator() {
-               return records.iterator();
-       }
-
        /**
         * Find the given record in the record list by identity and removes it
         *
@@ -89,19 +85,9 @@ public final class WorkbookRecordList implements Iterable<Record> {
           }
        }
 
-       public void remove( int pos )
-       {
+       public void remove( int pos ) {
                records.remove(pos);
-               if (getProtpos() >= pos) setProtpos( protpos - 1 );
-               if (getBspos() >= pos) setBspos( bspos - 1 );
-               if (getTabpos() >= pos) setTabpos( tabpos - 1 );
-               if (getFontpos() >= pos) setFontpos( fontpos - 1 );
-               if (getXfpos() >= pos) setXfpos( xfpos - 1 );
-               if (getBackuppos() >= pos) setBackuppos( backuppos - 1 );
-               if (getNamepos() >= pos) setNamepos(getNamepos()-1);
-               if (getSupbookpos() >= pos) setSupbookpos(getSupbookpos()-1);
-               if ((getPalettepos() != -1) && (getPalettepos() >= pos)) setPalettepos( palettepos - 1 );
-               if (getExternsheetPos() >= pos) setExternsheetPos( getExternsheetPos() -1);
+               updateRecordPos(pos, false);
        }
 
        public int getProtpos() {
@@ -160,7 +146,7 @@ public final class WorkbookRecordList implements Iterable<Record> {
                this.palettepos = palettepos;
        }
 
-       
+
        /**
         * Returns the namepos.
         * @return int
@@ -208,4 +194,48 @@ public final class WorkbookRecordList implements Iterable<Record> {
        public void setExternsheetPos(int externsheetPos) {
                this.externsheetPos = externsheetPos;
        }
+
+    private void updateRecordPos(int pos, boolean add) {
+        int delta = (add) ? 1 : -1;
+        int p = getProtpos();
+        if (p >= pos) {
+            setProtpos( p + delta );
+        }
+        p = getBspos();
+        if (p >= pos) {
+            setBspos( p + delta );
+        }
+        p = getTabpos();
+        if (p >= pos) {
+            setTabpos( p + delta );
+        }
+        p = getFontpos();
+        if (p >= pos) {
+            setFontpos( p + delta );
+        }
+        p = getXfpos();
+        if (p >= pos) {
+            setXfpos( p + delta );
+        }
+        p = getBackuppos();
+        if (p >= pos) {
+            setBackuppos( p + delta );
+        }
+        p = getNamepos();
+        if (p >= pos) {
+            setNamepos(p + delta );
+        }
+        p = getSupbookpos();
+        if (p >= pos) {
+            setSupbookpos(p + delta);
+        }
+        p = getPalettepos();
+        if (p != -1 && p >= pos) {
+            setPalettepos( p + delta );
+        }
+        p = getExternsheetPos();
+        if (p >= pos) {
+            setExternsheetPos( p + delta );
+        }
+    }
 }
index 4cfe3b9fe8354d5898af0de3fe3843d757870314..6fe2fd309d42085a9999d3d2ebaaf39d242d43bb 100644 (file)
@@ -30,6 +30,7 @@ import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.nio.charset.Charset;
+import java.security.GeneralSecurityException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -52,6 +53,8 @@ import org.apache.poi.ddf.EscherBlipRecord;
 import org.apache.poi.ddf.EscherMetafileBlip;
 import org.apache.poi.ddf.EscherRecord;
 import org.apache.poi.hpsf.ClassID;
+import org.apache.poi.hpsf.DocumentSummaryInformation;
+import org.apache.poi.hpsf.SummaryInformation;
 import org.apache.poi.hssf.OldExcelFormatException;
 import org.apache.poi.hssf.model.DrawingManager2;
 import org.apache.poi.hssf.model.HSSFFormulaParser;
@@ -59,6 +62,7 @@ import org.apache.poi.hssf.model.InternalSheet;
 import org.apache.poi.hssf.model.InternalSheet.UnsupportedBOFType;
 import org.apache.poi.hssf.model.InternalWorkbook;
 import org.apache.poi.hssf.model.RecordStream;
+import org.apache.poi.hssf.model.WorkbookRecordList;
 import org.apache.poi.hssf.record.AbstractEscherHolderRecord;
 import org.apache.poi.hssf.record.BackupRecord;
 import org.apache.poi.hssf.record.BoundSheetRecord;
@@ -77,9 +81,13 @@ import org.apache.poi.hssf.record.UnknownRecord;
 import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor;
 import org.apache.poi.hssf.record.common.UnicodeString;
 import org.apache.poi.hssf.record.crypto.Biff8DecryptingStream;
+import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
 import org.apache.poi.hssf.util.CellReference;
 import org.apache.poi.poifs.crypt.ChunkedCipherOutputStream;
 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.crypt.EncryptionVerifier;
 import org.apache.poi.poifs.crypt.Encryptor;
 import org.apache.poi.poifs.filesystem.DirectoryEntry;
 import org.apache.poi.poifs.filesystem.DirectoryNode;
@@ -1454,13 +1462,21 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
 
         // Write out our HPFS properties, if we have them
         writeProperties(fs, excepts);
-
+        
         if (preserveNodes) {
             // Don't write out the old Workbook, we'll be doing our new one
             // If the file had an "incorrect" name for the workbook stream,
             // don't write the old one as we'll use the correct name shortly
             excepts.addAll(Arrays.asList(WORKBOOK_DIR_ENTRY_NAMES));
 
+            // summary information has been already written via writeProperties and might go in a
+            // different stream, if the file is cryptoapi encrypted
+            excepts.addAll(Arrays.asList(
+                DocumentSummaryInformation.DEFAULT_STREAM_NAME,
+                SummaryInformation.DEFAULT_STREAM_NAME,
+                getEncryptedPropertyStreamName()
+            ));
+
             // Copy over all the other nodes to our new poifs
             EntryUtils.copyNodes(
                     new FilteringDirectoryNode(getDirectory(), excepts)
@@ -1520,6 +1536,7 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
         HSSFSheet[] sheets = getSheets();
         int nSheets = sheets.length;
 
+        updateEncryptionInfo();
 
         // before getting the workbook size we must tell the sheets that
         // serialization is about to occur.
@@ -1566,22 +1583,14 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
 
     @SuppressWarnings("resource")
     protected void encryptBytes(byte buf[]) {
-        int initialOffset = 0;
-        FilePassRecord fpr = null;
-        for (Record r : workbook.getRecords()) {
-            initialOffset += r.getRecordSize();
-            if (r instanceof FilePassRecord) {
-                fpr = (FilePassRecord)r;
-                break;
-            }
-        }
-        if (fpr == null) {
+        EncryptionInfo ei = getEncryptionInfo();
+        if (ei == null) {
             return;
         }
-        
+        Encryptor enc = ei.getEncryptor();
+        int initialOffset = 0;
         LittleEndianByteArrayInputStream plain = new LittleEndianByteArrayInputStream(buf, 0); // NOSONAR
         LittleEndianByteArrayOutputStream leos = new LittleEndianByteArrayOutputStream(buf, 0); // NOSONAR
-        Encryptor enc = fpr.getEncryptionInfo().getEncryptor();
         enc.setChunkSize(Biff8DecryptingStream.RC4_REKEYING_INTERVAL);
         byte tmp[] = new byte[1024];
         try {
@@ -2306,4 +2315,50 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
     public SpreadsheetVersion getSpreadsheetVersion() {
         return SpreadsheetVersion.EXCEL97;
     }
+
+    @Override
+    public EncryptionInfo getEncryptionInfo() {
+        FilePassRecord fpr = (FilePassRecord)workbook.findFirstRecordBySid(FilePassRecord.sid);
+        return (fpr != null) ? fpr.getEncryptionInfo() : null;
+    }
+
+
+    private void updateEncryptionInfo() {
+        // make sure, that we've read all the streams ...
+        readProperties();
+        FilePassRecord fpr = (FilePassRecord)workbook.findFirstRecordBySid(FilePassRecord.sid);
+
+        String password = Biff8EncryptionKey.getCurrentUserPassword();
+        WorkbookRecordList wrl = workbook.getWorkbookRecordList();
+        if (password == null) {
+            if (fpr != null) {
+                // need to remove password data
+                wrl.remove(fpr);
+            }
+        } else {
+            // create password record
+            if (fpr == null) {
+                fpr = new FilePassRecord(EncryptionMode.cryptoAPI);
+                wrl.add(1, fpr);
+            }
+
+            // check if the password has been changed
+            EncryptionInfo ei = fpr.getEncryptionInfo();
+            EncryptionVerifier ver = ei.getVerifier();
+            byte encVer[] = ver.getEncryptedVerifier();
+            Decryptor dec = ei.getDecryptor();
+            Encryptor enc = ei.getEncryptor();
+            try {
+                if (encVer == null || !dec.verifyPassword(password)) {
+                    enc.confirmPassword(password);
+                } else {
+                    byte verifier[] = dec.getVerifier();
+                    byte salt[] = ver.getSalt();
+                    enc.confirmPassword(password, null, null, verifier, salt, null);
+                }
+            } catch (GeneralSecurityException e) {
+                throw new EncryptedDocumentException("can't validate/update encryption setting", e);
+            }
+        }
+    }
 }
index 222b425178e47238db9a53db9f7f841ee907792e..f1b5e235ac7eac0457779c1a5fb727f6c245dfc1 100644 (file)
@@ -153,14 +153,19 @@ public class CryptoAPIDecryptor extends Decryptor implements Cloneable {
     /**
      * Decrypt the Document-/SummaryInformation and other optionally streams.
      * Opposed to other crypto modes, cryptoapi is record based and can't be used
-     * to stream-decrypt a whole file
+     * to stream-decrypt a whole file.<p>
+     * 
+     * Summary entries are only encrypted within cryptoapi encrypted files.
+     * Binary RC4 encrypted files use non-encrypted/default property sets
+     * 
+     * @param root root directory node of the OLE file containing the encrypted properties
+     * @param encryptedStream name of the encrypted stream -
+     *      "encryption" for HSSF/HWPF, "encryptedStream" (or encryptedSummary?) for HSLF 
      *
      * @see <a href="http://msdn.microsoft.com/en-us/library/dd943321(v=office.12).aspx">2.3.5.4 RC4 CryptoAPI Encrypted Summary Stream</a>
      */
     public POIFSFileSystem getSummaryEntries(DirectoryNode root, String encryptedStream)
     throws IOException, GeneralSecurityException {
-        // HSLF: encryptedStream
-        // HSSF: encryption
         DocumentNode es = (DocumentNode) root.getEntry(encryptedStream);
         DocumentInputStream dis = root.createDocumentInputStream(es);
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
index 2a47922883fa75ce61f72d1f595132df0a44566c..2dec4160347923474ac84d2bcd53164855cc3180 100644 (file)
@@ -32,10 +32,6 @@ import javax.crypto.Cipher;
 import javax.crypto.SecretKey;
 
 import org.apache.poi.EncryptedDocumentException;
-import org.apache.poi.hpsf.DocumentSummaryInformation;
-import org.apache.poi.hpsf.PropertySetFactory;
-import org.apache.poi.hpsf.SummaryInformation;
-import org.apache.poi.hpsf.WritingNotSupportedException;
 import org.apache.poi.poifs.crypt.ChunkedCipherOutputStream;
 import org.apache.poi.poifs.crypt.CryptoFunctions;
 import org.apache.poi.poifs.crypt.DataSpaceMapUtils;
@@ -46,6 +42,8 @@ import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIDecryptor.StreamDescriptorE
 import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
 import org.apache.poi.poifs.filesystem.DirectoryNode;
 import org.apache.poi.poifs.filesystem.DocumentInputStream;
+import org.apache.poi.poifs.filesystem.Entry;
+import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
 import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.LittleEndian;
 import org.apache.poi.util.LittleEndianByteArrayOutputStream;
@@ -124,41 +122,34 @@ public class CryptoAPIEncryptor extends Encryptor implements Cloneable {
      * 
      * @see <a href="http://msdn.microsoft.com/en-us/library/dd943321(v=office.12).aspx">2.3.5.4 RC4 CryptoAPI Encrypted Summary Stream</a>
      */
-    public OutputStream getSummaryEntries(DirectoryNode dir)
+    public void setSummaryEntries(DirectoryNode dir, String encryptedStream, NPOIFSFileSystem entries)
     throws IOException, GeneralSecurityException {
         CryptoAPIDocumentOutputStream bos = new CryptoAPIDocumentOutputStream(this); // NOSONAR
         byte buf[] = new byte[8];
         
         bos.write(buf, 0, 8); // skip header
-        String entryNames[] = {
-            SummaryInformation.DEFAULT_STREAM_NAME,
-            DocumentSummaryInformation.DEFAULT_STREAM_NAME
-        };
-        
         List<StreamDescriptorEntry> descList = new ArrayList<StreamDescriptorEntry>();
 
         int block = 0;
-        for (String entryName : entryNames) {
-            if (!dir.hasEntry(entryName)) {
+        for (Entry entry : entries.getRoot()) {
+            if (entry.isDirectoryEntry()) {
                 continue;
             }
             StreamDescriptorEntry descEntry = new StreamDescriptorEntry();
             descEntry.block = block;
             descEntry.streamOffset = bos.size();
-            descEntry.streamName = entryName;
+            descEntry.streamName = entry.getName();
             descEntry.flags = StreamDescriptorEntry.flagStream.setValue(0, 1);
             descEntry.reserved2 = 0;
             
             bos.setBlock(block);
-            DocumentInputStream dis = dir.createDocumentInputStream(entryName);
+            DocumentInputStream dis = dir.createDocumentInputStream(entry);
             IOUtils.copy(dis, bos);
             dis.close();
             
             descEntry.streamSize = bos.size() - descEntry.streamOffset;
             descList.add(descEntry);
             
-            dir.getEntry(entryName).delete();
-            
             block++;
         }
         
@@ -197,16 +188,7 @@ public class CryptoAPIEncryptor extends Encryptor implements Cloneable {
         bos.write(buf, 0, 8);
         bos.setSize(savedSize);
         
-        dir.createDocument("EncryptedSummary", new ByteArrayInputStream(bos.getBuf(), 0, savedSize));
-        DocumentSummaryInformation dsi = PropertySetFactory.newDocumentSummaryInformation();
-        
-        try {
-            dsi.write(dir, DocumentSummaryInformation.DEFAULT_STREAM_NAME);
-        } catch (WritingNotSupportedException e) {
-            throw new IOException(e);
-        }
-        
-        return bos;
+        dir.createDocument(encryptedStream, new ByteArrayInputStream(bos.getBuf(), 0, savedSize));
     }
 
     protected int getKeySizeInBytes() {
diff --git a/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestHxxFEncryption.java b/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestHxxFEncryption.java
new file mode 100644 (file)
index 0000000..c9be3d9
--- /dev/null
@@ -0,0 +1,187 @@
+/* ====================================================================
+   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.poifs.crypt;
+
+import static org.apache.poi.POIDataSamples.getDocumentInstance;
+import static org.apache.poi.POIDataSamples.getSlideShowInstance;
+import static org.apache.poi.POIDataSamples.getSpreadSheetInstance;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.apache.poi.POIDataSamples;
+import org.apache.poi.POIDocument;
+import org.apache.poi.POIOLE2TextExtractor;
+import org.apache.poi.POITextExtractor;
+import org.apache.poi.extractor.ExtractorFactory;
+import org.apache.poi.hslf.usermodel.HSLFSlideShowImpl;
+import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
+import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
+import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptionHeader;
+import org.apache.poi.poifs.storage.RawDataUtil;
+import org.apache.xmlbeans.XmlException;
+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 TestHxxFEncryption {
+    @AfterClass
+    public static void clearPass() {
+        Biff8EncryptionKey.setCurrentUserPassword(null);
+    }
+
+    @Parameter(value = 0)
+    public POIDataSamples sampleDir;
+
+    @Parameter(value = 1)
+    public String file;
+
+    @Parameter(value = 2)
+    public String password;
+
+    @Parameter(value = 3)
+    public String expected;
+
+    @Parameters(name="{1}")
+    public static Collection<Object[]> data() throws IOException {
+        return Arrays.asList(
+            // binary rc4
+            new Object[]{ getDocumentInstance(), "password_tika_binaryrc4.doc", "tika", "This is an encrypted Word 2007 File." },
+            // cryptoapi
+            new Object[]{ getDocumentInstance(), "password_password_cryptoapi.doc", "password", "This is a test" },
+            // binary rc4
+            new Object[]{ getSpreadSheetInstance(), "password.xls", "password",
+                x("H4sIAAAAAAAAAF1Uu24bMRDs/RULVwkgCUhSpHaZwkDgpHJH8fZ0G/Nx4ZI6y13yG/mRfIb9R5mlZFlIpdPtcnZmdnjPf57/vvx6+f3h6obuv3"+
+                  "ylbY5bEiVHe1fEpUp5pOgkrK0iabehm7FyoZi1ks8xcvHiQu8h5bLnorTlnUvkJ/YPOHKsLVInAqCs91KakuaxLq4w3g00SgCo9Xou1UnCmSBe"+
+                  "MhpRY6qHmXVFteQfQJ5yUaaOw4qXwgPVjPGAqhNH5bBHAfTmwqqoSkLdFT/J3nC0eZBRk7yiu5s7yoU+r+9l3tDtm5A3jgt6AQxNOY2ya+U4sK"+
+                  "XZ+YczbpfSVVuzFOuunKraqIVD2ND3yVXauT3TNthR/O3IJAM7gzTOGeIcXZvj14ahotW8wSognlMu0Yyp/Fi7O6s+CK6haUUjtPCji7MVcgqH"+
+                  "jh+42tqeqPDMroJ/lBAE4AZbJbJu6Fu35ej42Tw9mYeTwVXoBKJiPeFV94q2rZJAyNEPo/qOdWYLBpq3B2JX8GDZeJ14mZf3tOQWBmpd9yQ7kI"+
+                  "DCY/jmkj1oGOicFy62r9vutC5uJsVEMFgmAXXfYcC6BRBKNHCybALFJolnrDcPXNLl+K60Vctt09YZT7YgbeOICGJ/ZgC2JztOnm1JhX3eJXni"+
+                  "U5Bqhezzlu334vD/Ajr3yDGXw5G9IZ6aLmLfQafY42N3J7cjj1LaXOHihSrcC5ThmuYIB5FX5AU8tKlnNG9Dn1EnsdD4KcnPhsSNPRiXtz461b"+
+                  "VZw8Pm6vn0afh4fvr0D5P/+cMuBAAA") },
+            // cryptoapi
+            new Object[]{ getSpreadSheetInstance(), "35897-type4.xls", "freedom", "Sheet1\nhello there!" },
+            // cryptoapi (PPT only supports cryptoapi...)
+            new Object[]{ getSlideShowInstance(), "cryptoapi-proc2356.ppt", "crypto", "Dominic Salemno" }
+        );
+    }
+
+    private static String x(String base64) throws IOException {
+        return new String(RawDataUtil.decompress(base64), Charset.forName("UTF-8"));
+    }
+    
+    @Test
+    public void extract() throws IOException, OpenXML4JException, XmlException {
+        Biff8EncryptionKey.setCurrentUserPassword(password);
+        File f = sampleDir.getFile(file);
+        POITextExtractor te = ExtractorFactory.createExtractor(f);
+        String actual = te.getText().trim();
+        assertEquals(expected, actual);
+        te.close();
+    }
+    
+    @Test
+    public void changePassword() throws IOException, OpenXML4JException, XmlException {
+        newPassword("test");
+    }
+    
+    @Test
+    public void removePassword() throws IOException, OpenXML4JException, XmlException {
+        newPassword(null);
+    }
+    
+    public void newPassword(String newPass) throws IOException, OpenXML4JException, XmlException {
+        Biff8EncryptionKey.setCurrentUserPassword(password);
+        File f = sampleDir.getFile(file);
+        POIOLE2TextExtractor te1 = (POIOLE2TextExtractor)ExtractorFactory.createExtractor(f);
+        Biff8EncryptionKey.setCurrentUserPassword(newPass);
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        POIDocument doc = te1.getDocument();
+        doc.write(bos);
+        doc.close();
+        te1.close();
+        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
+        POITextExtractor te2 = ExtractorFactory.createExtractor(bis);
+        String actual = te2.getText().trim();
+        assertEquals(expected, actual);
+        te2.close();
+    }
+
+    /** changing the encryption mode and key size in poor mans style - see comments below */
+    @Test
+    public void changeEncryption() throws IOException, OpenXML4JException, XmlException {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        Biff8EncryptionKey.setCurrentUserPassword(password);
+        File f = sampleDir.getFile(file);
+        POIOLE2TextExtractor te1 = (POIOLE2TextExtractor)ExtractorFactory.createExtractor(f);
+        // first remove encryption
+        Biff8EncryptionKey.setCurrentUserPassword(null);
+        POIDocument doc = te1.getDocument();
+        doc.write(bos);
+        doc.close();
+        te1.close();
+        // then use default setting, which is cryptoapi
+        String newPass = "newPass";
+        POIOLE2TextExtractor te2 = (POIOLE2TextExtractor)ExtractorFactory.createExtractor(new ByteArrayInputStream(bos.toByteArray()));
+        Biff8EncryptionKey.setCurrentUserPassword(newPass);
+        doc = te2.getDocument();
+        bos.reset();
+        doc.write(bos);
+        doc.close();
+        te2.close();
+        // and finally update cryptoapi setting
+        POIOLE2TextExtractor te3 = (POIOLE2TextExtractor)ExtractorFactory.createExtractor(new ByteArrayInputStream(bos.toByteArray()));
+        doc = te3.getDocument();
+        // need to cache data (i.e. read all data) before changing the key size
+        if (doc instanceof HSLFSlideShowImpl) {
+            HSLFSlideShowImpl hss = (HSLFSlideShowImpl)doc;
+            hss.getPictureData();
+            hss.getDocumentSummaryInformation();
+        }
+        EncryptionInfo ei = doc.getEncryptionInfo();
+        assertNotNull(ei);
+        assertTrue(ei.getHeader() instanceof CryptoAPIEncryptionHeader);
+        assertEquals(0x28, ((CryptoAPIEncryptionHeader)ei.getHeader()).getKeySize());
+        ((CryptoAPIEncryptionHeader)ei.getHeader()).setKeySize(0x78);
+        bos.reset();
+        doc.write(bos);
+        doc.close();
+        te3.close();
+        // check the setting
+        POIOLE2TextExtractor te4 = (POIOLE2TextExtractor)ExtractorFactory.createExtractor(new ByteArrayInputStream(bos.toByteArray()));
+        doc = te4.getDocument();
+        ei = doc.getEncryptionInfo();
+        assertNotNull(ei);
+        assertTrue(ei.getHeader() instanceof CryptoAPIEncryptionHeader);
+        assertEquals(0x78, ((CryptoAPIEncryptionHeader)ei.getHeader()).getKeySize());
+        doc.close();
+        te4.close();
+    }
+}
index 1251904b21f79a54a461f384cf7c9ffeb59af2f3..83925bc540d3bc5e785e12ab01a71475368e79ed 100644 (file)
@@ -40,8 +40,7 @@ import org.apache.poi.poifs.crypt.ChunkedCipherInputStream;
 import org.apache.poi.poifs.crypt.ChunkedCipherOutputStream;
 import org.apache.poi.poifs.crypt.Decryptor;
 import org.apache.poi.poifs.crypt.EncryptionInfo;
-import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIDecryptor;
-import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptor;
+import org.apache.poi.poifs.crypt.Encryptor;
 import org.apache.poi.util.BitField;
 import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.Internal;
@@ -55,8 +54,7 @@ import org.apache.poi.util.LittleEndianByteArrayOutputStream;
 @Internal
 public class HSLFSlideShowEncrypted implements Closeable {
     DocumentEncryptionAtom dea;
-    CryptoAPIEncryptor enc = null;
-    CryptoAPIDecryptor dec = null;
+    EncryptionInfo _encryptionInfo;
 //    Cipher cipher = null;
     ChunkedCipherOutputStream cyos = null;
 
@@ -120,11 +118,15 @@ public class HSLFSlideShowEncrypted implements Closeable {
         }
         assert(r instanceof DocumentEncryptionAtom);
         this.dea = (DocumentEncryptionAtom)r;
-        decryptInit();
 
         String pass = Biff8EncryptionKey.getCurrentUserPassword();
-        if(!dec.verifyPassword(pass != null ? pass : Decryptor.DEFAULT_PASSWORD)) {
-            throw new EncryptedPowerPointFileException("PowerPoint file is encrypted. The correct password needs to be set via Biff8EncryptionKey.setCurrentUserPassword()");
+        EncryptionInfo ei = getEncryptionInfo();
+        try {
+            if(!ei.getDecryptor().verifyPassword(pass != null ? pass : Decryptor.DEFAULT_PASSWORD)) {
+                throw new EncryptedPowerPointFileException("PowerPoint file is encrypted. The correct password needs to be set via Biff8EncryptionKey.setCurrentUserPassword()");
+            }
+        } catch (GeneralSecurityException e) {
+            throw new EncryptedPowerPointFileException(e);
         }
      }
 
@@ -132,24 +134,10 @@ public class HSLFSlideShowEncrypted implements Closeable {
         return dea;
     }
 
-    protected void decryptInit() {
-        if (dec != null) {
-            return;
-        }
-        EncryptionInfo ei = dea.getEncryptionInfo();
-        dec = (CryptoAPIDecryptor)ei.getDecryptor();
-    }
-
-    protected void encryptInit() {
-        if (enc != null) {
-            return;
-        }
-        EncryptionInfo ei = dea.getEncryptionInfo();
-        enc = (CryptoAPIEncryptor)ei.getEncryptor();
+    protected EncryptionInfo getEncryptionInfo() {
+        return (dea != null) ? dea.getEncryptionInfo() : null;
     }
 
-
-
     protected OutputStream encryptRecord(OutputStream plainStream, int persistId, Record record) {
         boolean isPlain = (dea == null
             || record instanceof UserEditAtom
@@ -166,9 +154,8 @@ public class HSLFSlideShowEncrypted implements Closeable {
                 return plainStream;
             }
 
-            encryptInit();
-
             if (cyos == null) {
+                Encryptor enc = getEncryptionInfo().getEncryptor();
                 enc.setChunkSize(-1);
                 cyos = enc.getDataStream(plainStream, 0);
             }
@@ -190,12 +177,12 @@ public class HSLFSlideShowEncrypted implements Closeable {
             return;
         }
 
-        decryptInit();
+        Decryptor dec = getEncryptionInfo().getDecryptor();
         dec.setChunkSize(-1);
         LittleEndianByteArrayInputStream lei = new LittleEndianByteArrayInputStream(docstream, offset); // NOSONAR
         ChunkedCipherInputStream ccis = null;
         try {
-            ccis = dec.getDataStream(lei, docstream.length-offset, 0);
+            ccis = (ChunkedCipherInputStream)dec.getDataStream(lei, docstream.length-offset, 0);
             ccis.initCipherForBlock(persistId);
 
             // decrypt header and read length to be decrypted
@@ -217,7 +204,8 @@ public class HSLFSlideShowEncrypted implements Closeable {
         // when reading the picture elements, each time a segment is read, the cipher needs
         // to be reset (usually done when calling Cipher.doFinal)
         LittleEndianByteArrayInputStream lei = new LittleEndianByteArrayInputStream(pictstream, offset);
-        ChunkedCipherInputStream ccis = dec.getDataStream(lei, len, 0);
+        Decryptor dec = getEncryptionInfo().getDecryptor();
+        ChunkedCipherInputStream ccis = (ChunkedCipherInputStream)dec.getDataStream(lei, len, 0);
         readFully(ccis, pictstream, offset, len);
         ccis.close();
         lei.close();
@@ -228,8 +216,6 @@ public class HSLFSlideShowEncrypted implements Closeable {
             return;
         }
 
-        decryptInit();
-
         try {
             // decrypt header and read length to be decrypted
             decryptPicBytes(pictstream, offset, 8);
@@ -302,12 +288,11 @@ public class HSLFSlideShowEncrypted implements Closeable {
             return;
         }
 
-        encryptInit();
-
         LittleEndianByteArrayOutputStream los = new LittleEndianByteArrayOutputStream(pictstream, offset); // NOSONAR
         ChunkedCipherOutputStream ccos = null;
 
         try {
+            Encryptor enc = getEncryptionInfo().getEncryptor();
             enc.setChunkSize(-1);
             ccos = enc.getDataStream(los, 0);
             int recInst = fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset));
@@ -396,11 +381,10 @@ public class HSLFSlideShowEncrypted implements Closeable {
             // create password record
             if (dea == null) {
                 dea = new DocumentEncryptionAtom();
-                enc = null;
             }
-            encryptInit();
             EncryptionInfo ei = dea.getEncryptionInfo();
             byte salt[] = ei.getVerifier().getSalt();
+            Encryptor enc = getEncryptionInfo().getEncryptor();
             if (salt == null) {
                 enc.confirmPassword(password);
             } else {
index 02e3d3e0ccf8dbd6ac29cdc6d061edbdc99ab275..2b758cff3dff1ec85302847580fde669480a4053 100644 (file)
@@ -24,7 +24,6 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.security.GeneralSecurityException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -34,7 +33,6 @@ import java.util.NavigableMap;
 import java.util.TreeMap;
 
 import org.apache.poi.POIDocument;
-import org.apache.poi.hpsf.PropertySet;
 import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException;
 import org.apache.poi.hslf.exceptions.HSLFException;
 import org.apache.poi.hslf.record.CurrentUserAtom;
@@ -46,7 +44,7 @@ import org.apache.poi.hslf.record.PositionDependentRecord;
 import org.apache.poi.hslf.record.Record;
 import org.apache.poi.hslf.record.RecordTypes;
 import org.apache.poi.hslf.record.UserEditAtom;
-import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptor;
+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.DocumentInputStream;
@@ -731,44 +729,15 @@ public final class HSLFSlideShowImpl extends POIDocument implements Closeable {
         }
     }
 
-    /**
-     * 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) {
-        DocumentEncryptionAtom dea = getDocumentEncryptionAtom();
-        return (dea == null)
-                ? super.getPropertySet(setName)
-                : super.getPropertySet(setName, dea.getEncryptionInfo());
-    }
 
-    /**
-     * Writes out the standard Documment Information Properties (HPSF)
-     *
-     * @param outFS          the POIFSFileSystem to write the properties into
-     * @param writtenEntries a list of POIFS entries to add the property names too
-     * @throws IOException if an error when writing to the
-     *                     {@link POIFSFileSystem} occurs
-     */
     @Override
-    protected void writeProperties(NPOIFSFileSystem outFS, List<String> writtenEntries) throws IOException {
-        super.writeProperties(outFS, writtenEntries);
+    public EncryptionInfo getEncryptionInfo() throws IOException {
         DocumentEncryptionAtom dea = getDocumentEncryptionAtom();
-        if (dea != null) {
-            CryptoAPIEncryptor enc = (CryptoAPIEncryptor) dea.getEncryptionInfo().getEncryptor();
-            try {
-                enc.getSummaryEntries(outFS.getRoot()); // ignore OutputStream
-            } catch (IOException e) {
-                throw e;
-            } catch (GeneralSecurityException e) {
-                throw new IOException(e);
-            }
-        }
+        return (dea != null) ? dea.getEncryptionInfo() : null;
     }
+
+    
+    
     
     /* ******************* adding methods follow ********************* */
 
@@ -893,6 +862,10 @@ public final class HSLFSlideShowImpl extends POIDocument implements Closeable {
         }
     }
 
+    @Override
+    protected String getEncryptedPropertyStreamName() {
+        return "EncryptedSummary";
+    }
 
     private static class BufAccessBAOS extends ByteArrayOutputStream {
         public byte[] getBuf() {
index 25e83b95ef7aee0b8c0085ce5c6762d9d1bb360a..1369841acf7bb0c7803b1ec8c7f4f6482e5bad55 100644 (file)
@@ -23,6 +23,7 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.security.GeneralSecurityException;
 
 import org.apache.poi.hpsf.DocumentSummaryInformation;
 import org.apache.poi.hpsf.SummaryInformation;
@@ -61,13 +62,18 @@ 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.ChunkedCipherOutputStream;
 import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.poifs.crypt.EncryptionMode;
+import org.apache.poi.poifs.crypt.Encryptor;
+import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
 import org.apache.poi.poifs.filesystem.DirectoryNode;
 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.Internal;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
 
 /**
  *
@@ -322,6 +328,7 @@ public final class HWPFDocument extends HWPFDocumentCore {
         _fields = new FieldsImpl(_fieldsTables);
     }
 
+    @Override
     @Internal
     public TextPieceTable getTextTable()
     {
@@ -340,6 +347,7 @@ public final class HWPFDocument extends HWPFDocumentCore {
         return _dop;
     }
 
+    @Override
     public Range getOverallRange() {
         return new Range(0, _text.length(), this);
     }
@@ -348,6 +356,7 @@ public final class HWPFDocument extends HWPFDocumentCore {
      * Returns the range which covers the whole of the document, but excludes
      * any headers and footers.
      */
+    @Override
     public Range getRange()
     {
         // // First up, trigger a full-recalculate
@@ -394,8 +403,9 @@ public final class HWPFDocument extends HWPFDocumentCore {
         {
             int length = getFileInformationBlock()
                     .getSubdocumentTextStreamLength( previos );
-            if ( subdocument == previos )
+            if ( subdocument == previos ) {
                 return new Range( startCp, startCp + length, this );
+            }
             startCp += length;
         }
         throw new UnsupportedOperationException(
@@ -603,34 +613,52 @@ public final class HWPFDocument extends HWPFDocumentCore {
      * @throws IOException If there is an unexpected IOException from the passed
      *         in OutputStream.
      */
+    @Override
     public void write(OutputStream out) throws IOException {
         NPOIFSFileSystem pfs = new NPOIFSFileSystem();
         write(pfs, true);
         pfs.writeFilesystem( out );
     }
-    private void write(NPOIFSFileSystem pfs, boolean copyOtherEntries) throws IOException {
-        // initialize our streams for writing.
-        HWPFFileSystem docSys = new HWPFFileSystem();
-        ByteArrayOutputStream wordDocumentStream = docSys.getStream(STREAM_WORD_DOCUMENT);
-        ByteArrayOutputStream tableStream = docSys.getStream(STREAM_TABLE_1);
-        //HWPFOutputStream dataStream = docSys.getStream("Data");
-        int tableOffset = 0;
 
-        // FileInformationBlock fib = (FileInformationBlock)_fib.clone();
+    private void write(NPOIFSFileSystem pfs, boolean copyOtherEntries) throws IOException {
         // clear the offsets and sizes in our FileInformationBlock.
         _fib.clearOffsetsSizes();
 
         // determine the FileInformationBLock size
         int fibSize = _fib.getSize();
-        fibSize  += POIFSConstants.SMALLER_BIG_BLOCK_SIZE -
-                (fibSize % POIFSConstants.SMALLER_BIG_BLOCK_SIZE);
+        fibSize += POIFSConstants.SMALLER_BIG_BLOCK_SIZE - (fibSize % POIFSConstants.SMALLER_BIG_BLOCK_SIZE);
+
+        // initialize our streams for writing.
+        HWPFFileSystem docSys = new HWPFFileSystem();
+        ByteArrayOutputStream wordDocumentStream = docSys.getStream(STREAM_WORD_DOCUMENT);
+        ByteArrayOutputStream tableStream = docSys.getStream(STREAM_TABLE_1);
 
         // preserve space for the FileInformationBlock because we will be writing
         // it after we write everything else.
         byte[] placeHolder = new byte[fibSize];
         wordDocumentStream.write(placeHolder);
         int mainOffset = wordDocumentStream.size();
+        int tableOffset = 0;
 
+        // write out EncryptionInfo
+        updateEncryptionInfo();
+        EncryptionInfo ei = getEncryptionInfo();
+        if (ei != null) {
+            byte buf[] = new byte[1000];
+            LittleEndianByteArrayOutputStream leos = new LittleEndianByteArrayOutputStream(buf, 0);
+            leos.writeShort(ei.getVersionMajor());
+            leos.writeShort(ei.getVersionMinor());
+            if (ei.getEncryptionMode() == EncryptionMode.cryptoAPI) {
+                leos.writeInt(ei.getEncryptionFlags());
+            }
+            
+            ((EncryptionRecord)ei.getHeader()).write(leos);
+            ((EncryptionRecord)ei.getVerifier()).write(leos);
+            tableStream.write(buf, 0, leos.getWriteIndex());
+            tableOffset += leos.getWriteIndex();
+            _fib.getFibBase().setLKey(tableOffset);
+        }
+        
         // write out the StyleSheet.
         _fib.setFcStshf(tableOffset);
         _ss.writeTo(tableStream);
@@ -869,13 +897,7 @@ public final class HWPFDocument extends HWPFDocumentCore {
         _fib.setCbMac(wordDocumentStream.size());
 
         // make sure that the table, doc and data streams use big blocks.
-        byte[] mainBuf = wordDocumentStream.toByteArray();
-        if (mainBuf.length < 4096)
-        {
-            byte[] tempBuf = new byte[4096];
-            System.arraycopy(mainBuf, 0, tempBuf, 0, mainBuf.length);
-            mainBuf = tempBuf;
-        }
+        byte[] mainBuf = fillUp4096(wordDocumentStream);
 
         // Table1 stream will be used
         _fib.getFibBase().setFWhichTblStm( true );
@@ -884,98 +906,52 @@ public final class HWPFDocument extends HWPFDocumentCore {
         //_fib.serialize(mainBuf, 0);
         _fib.writeTo(mainBuf, tableStream);
 
-        byte[] tableBuf = tableStream.toByteArray();
-        if (tableBuf.length < 4096)
-        {
-            byte[] tempBuf = new byte[4096];
-            System.arraycopy(tableBuf, 0, tempBuf, 0, tableBuf.length);
-            tableBuf = tempBuf;
-        }
-
-        byte[] dataBuf = _dataStream;
-        if (dataBuf == null)
-        {
-            dataBuf = new byte[4096];
-        }
-        if (dataBuf.length < 4096)
-        {
-            byte[] tempBuf = new byte[4096];
-            System.arraycopy(dataBuf, 0, tempBuf, 0, dataBuf.length);
-            dataBuf = tempBuf;
+        byte[] tableBuf = fillUp4096(tableStream);
+        byte[] dataBuf = fillUp4096(_dataStream);
+        
+        // Create a new document - ignoring the order of the old entries
+        if (ei == null) {
+            write(pfs, mainBuf, STREAM_WORD_DOCUMENT);
+            write(pfs, tableBuf, STREAM_TABLE_1);
+            write(pfs, dataBuf, STREAM_DATA);
+        } else {
+            ByteArrayOutputStream bos = new ByteArrayOutputStream(100000);
+            encryptBytes(mainBuf, FIB_BASE_LEN, bos);
+            write(pfs, bos.toByteArray(), STREAM_WORD_DOCUMENT);
+            bos.reset();
+            encryptBytes(tableBuf, _fib.getFibBase().getLKey(), bos);
+            write(pfs, bos.toByteArray(), STREAM_TABLE_1);
+            bos.reset();
+            encryptBytes(dataBuf, 0, bos);
+            write(pfs, bos.toByteArray(), STREAM_DATA);
+            bos.reset();
         }
 
-        // Create a new document preserving order of entries / Update existing
-        boolean docWritten = false;
-        boolean dataWritten = false;
-        boolean objectPoolWritten = false;
-        boolean tableWritten = false;
-        boolean propertiesWritten = false;
-        for (Entry entry : getDirectory()) {
-            if ( entry.getName().equals( STREAM_WORD_DOCUMENT ) )
-            {
-                if ( !docWritten )
-                {
-                    write(pfs, mainBuf, STREAM_WORD_DOCUMENT);
-                    docWritten = true;
-                }
-            }
-            else if ( entry.getName().equals( STREAM_OBJECT_POOL ) )
-            {
-                if ( !objectPoolWritten )
-                {
-                    if ( copyOtherEntries ) {
-                        _objectPool.writeTo( pfs.getRoot() );
-                    } else {
-                        // Object pool is already there, no need to change/copy
-                    }
-                    objectPoolWritten = true;
-                }
-            }
-            else if ( entry.getName().equals( STREAM_TABLE_0 )
-                    || entry.getName().equals( STREAM_TABLE_1 ) )
-            {
-                if ( !tableWritten )
-                {
-                    write(pfs, tableBuf, STREAM_TABLE_1);
-                    tableWritten = true;
-                }
-            }
-            else if ( entry.getName().equals(
-                    SummaryInformation.DEFAULT_STREAM_NAME )
-                    || entry.getName().equals(
-                            DocumentSummaryInformation.DEFAULT_STREAM_NAME ) )
-            {
-                if ( !propertiesWritten )
-                {
-                    writeProperties( pfs );
-                    propertiesWritten = true;
-                }
-            }
-            else if ( entry.getName().equals( STREAM_DATA ) )
-            {
-                if ( !dataWritten )
-                {
-                    write(pfs, dataBuf, STREAM_DATA);
-                    dataWritten = true;
+        writeProperties( pfs );
+        
+        if ( copyOtherEntries && ei == null ) {
+            // For encrypted files:
+            // The ObjectPool storage MUST NOT be present and if the file contains OLE objects, the storage
+            // objects for the OLE objects MUST be stored in the Data stream as specified in sprmCPicLocation.
+            DirectoryNode newRoot = pfs.getRoot();
+            _objectPool.writeTo( newRoot );
+        
+            for (Entry entry : getDirectory()) {
+                String entryName = entry.getName(); 
+                if ( !(
+                    STREAM_WORD_DOCUMENT.equals(entryName) ||
+                    STREAM_TABLE_0.equals(entryName) ||
+                    STREAM_TABLE_1.equals(entryName) ||
+                    STREAM_DATA.equals(entryName) ||
+                    STREAM_OBJECT_POOL.equals(entryName) ||
+                    SummaryInformation.DEFAULT_STREAM_NAME.equals(entryName) ||
+                    DocumentSummaryInformation.DEFAULT_STREAM_NAME.equals(entryName)
+                ) ) {
+                    EntryUtils.copyNodeRecursively( entry, newRoot );
                 }
             }
-            else if ( copyOtherEntries )
-            {
-                EntryUtils.copyNodeRecursively( entry, pfs.getRoot() );
-            }
         }
 
-        if ( !docWritten )
-            write(pfs, mainBuf, STREAM_WORD_DOCUMENT);
-        if ( !tableWritten )
-            write(pfs, tableBuf, STREAM_TABLE_1);
-        if ( !propertiesWritten )
-            writeProperties( pfs );
-        if ( !dataWritten )
-            write(pfs, dataBuf, STREAM_DATA);
-        if ( !objectPoolWritten && copyOtherEntries )
-            _objectPool.writeTo( pfs.getRoot() );
-
         /*
          * since we updated all references in FIB and etc, using new arrays to
          * access data
@@ -984,6 +960,43 @@ public final class HWPFDocument extends HWPFDocumentCore {
         this._tableStream = tableStream.toByteArray();
         this._dataStream = dataBuf;
     }
+        
+    private void encryptBytes(byte[] plain, int encryptOffset, OutputStream bos) throws IOException {
+        try {
+            EncryptionInfo ei = getEncryptionInfo();
+            Encryptor enc = ei.getEncryptor();
+            enc.setChunkSize(RC4_REKEYING_INTERVAL);
+            ChunkedCipherOutputStream os = enc.getDataStream(bos, 0);
+            if (encryptOffset > 0) {
+                os.writePlain(plain, 0, encryptOffset);
+            }
+            os.write(plain, encryptOffset, plain.length-encryptOffset);
+            os.close();
+        } catch (GeneralSecurityException e) {
+            throw new IOException(e);
+        }
+    }
+    
+    private static byte[] fillUp4096(byte[] buf) {
+        if (buf == null) {
+            return new byte[4096];
+        } else if (buf.length < 4096) {
+            ByteArrayOutputStream bos = new ByteArrayOutputStream(4096);
+            bos.write(buf, 0, buf.length);
+            return fillUp4096(bos);
+        } else {
+            return buf;
+        }
+    }
+    
+    private static byte[] fillUp4096(ByteArrayOutputStream bos) {
+        int fillSize = 4096 - bos.size();
+        if (fillSize > 0) {
+            bos.write(new byte[fillSize], 0, fillSize);
+        }
+        return bos.toByteArray();
+    }
+    
     private static void write(NPOIFSFileSystem pfs, byte[] data, String name) throws IOException {
         pfs.createOrUpdateDocument(new ByteArrayInputStream(data), name);
     }
@@ -1009,9 +1022,8 @@ public final class HWPFDocument extends HWPFDocumentCore {
                 list.getLFOData() );
     }
 
-  public void delete(int start, int length)
-  {
-    Range r = new Range(start, start + length, this);
-    r.delete();
-  }
-}
+    public void delete(int start, int length) {
+        Range r = new Range(start, start + length, this);
+        r.delete();
+    }
+}
\ No newline at end of file
index c52abc101ee35c719d8c5de7b4fb188f1e4139b6..42639decbb7747576c76e26b330f9fb263c04a04 100644 (file)
@@ -18,7 +18,6 @@
 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;
@@ -26,7 +25,6 @@ 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;
@@ -44,6 +42,7 @@ 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.crypt.Encryptor;
 import org.apache.poi.poifs.filesystem.DirectoryEntry;
 import org.apache.poi.poifs.filesystem.DirectoryNode;
 import org.apache.poi.poifs.filesystem.DocumentEntry;
@@ -67,7 +66,17 @@ public abstract class HWPFDocumentCore extends POIDocument {
     protected static final String STREAM_TABLE_0 = "0Table";
     protected static final String STREAM_TABLE_1 = "1Table";
 
-    private static final int FIB_BASE_LEN = 68;
+    /**
+     * Size of the not encrypted part of the FIB
+     */
+    protected static final int FIB_BASE_LEN = 68;
+    
+    /**
+     * [MS-DOC] 2.2.6.2/3 Office Binary Document ... Encryption:
+     * "... The block number MUST be set to zero at the beginning of the stream and
+     * MUST be incremented at each 512 byte boundary. ..."
+     */
+    protected static final int RC4_REKEYING_INTERVAL = 512;
 
     /** Holds OLE2 objects */
     protected ObjectPoolImpl _objectPool;
@@ -171,35 +180,85 @@ public abstract class HWPFDocumentCore extends POIDocument {
         }
         _objectPool = new ObjectPoolImpl(objectPoolEntry);
     }
+    /**
+     * Returns the range which covers the whole of the document, but excludes
+     * any headers and footers.
+     */
+    public abstract Range getRange();
 
     /**
-     * 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.
+     * Returns the range that covers all text in the file, including main text,
+     * footnotes, headers and comments
      */
-    @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);
+    public abstract Range getOverallRange();
+
+    /**
+     * Returns document text, i.e. text information from all text pieces,
+     * including OLE descriptions and field codes
+     */
+    public String getDocumentText() {
+        return getText().toString();
+    }
+
+    /**
+     * Internal method to access document text
+     */
+    @Internal
+    public abstract StringBuilder getText();
+
+    public CHPBinTable getCharacterTable() {
+        return _cbt;
     }
 
-    protected EncryptionInfo getEncryptionInfo() throws IOException {
+    public PAPBinTable getParagraphTable() {
+        return _pbt;
+    }
+
+    public SectionTable getSectionTable() {
+        return _st;
+    }
+
+    public StyleSheet getStyleSheet() {
+        return _ss;
+    }
+
+    public ListTables getListTables() {
+        return _lt;
+    }
+
+    public FontTable getFontTable() {
+        return _ft;
+    }
+
+    public FileInformationBlock getFileInformationBlock() {
+        return _fib;
+    }
+
+    public ObjectsPool getObjectsPool() {
+        return _objectPool;
+    }
+
+    public abstract TextPieceTable getTextTable();
+
+    @Internal
+    public byte[] getMainStream() {
+        return _mainStream;
+    }
+
+    @Override
+    public 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 );
+        FibBase fibBase;
+        if (_fib != null && _fib.getFibBase() != null) {
+            fibBase = _fib.getFibBase();
+        } else {
+            byte[] fibBaseBytes = (_mainStream != null) ? _mainStream : getDocumentEntryBytes(STREAM_WORD_DOCUMENT, -1, FIB_BASE_LEN);
+            fibBase = new FibBase( fibBaseBytes, 0 );
+        }
         if (!fibBase.isFEncrypted()) {
             return null;
         }
@@ -210,7 +269,7 @@ public abstract class HWPFDocumentCore extends POIDocument {
         EncryptionMode em = fibBase.isFObfuscated() ? EncryptionMode.xor : null;
         EncryptionInfo ei = new EncryptionInfo(leis, em);
         Decryptor dec = ei.getDecryptor();
-        dec.setChunkSize(512);
+        dec.setChunkSize(RC4_REKEYING_INTERVAL);
         try {
             String pass = Biff8EncryptionKey.getCurrentUserPassword();
             if (pass == null) {
@@ -226,6 +285,35 @@ public abstract class HWPFDocumentCore extends POIDocument {
         return ei;
     }
 
+    protected void updateEncryptionInfo() {
+        // make sure, that we've read all the streams ...
+        readProperties();
+        // now check for the password
+        String password = Biff8EncryptionKey.getCurrentUserPassword();
+        FibBase fBase = _fib.getFibBase();
+        if (password == null) {
+            fBase.setLKey(0);
+            fBase.setFEncrypted(false);
+            fBase.setFObfuscated(false);
+            _encryptionInfo = null;
+        } else {
+            // create password record
+            if (_encryptionInfo == null) {
+                _encryptionInfo = new EncryptionInfo(EncryptionMode.cryptoAPI);
+                fBase.setFEncrypted(true);
+                fBase.setFObfuscated(false);
+            }
+            Encryptor enc = _encryptionInfo.getEncryptor();
+            byte salt[] = _encryptionInfo.getVerifier().getSalt();
+            if (salt == null) {
+                enc.confirmPassword(password);
+            } else {
+                byte verifier[] = _encryptionInfo.getDecryptor().getVerifier();
+                enc.confirmPassword(password, null, null, verifier, salt, null);
+            }
+        }
+    }
+
     /**
      * 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
@@ -273,70 +361,4 @@ public abstract class HWPFDocumentCore extends POIDocument {
             IOUtils.closeQuietly(dis);
         }
     }
-
-
-    /**
-     * Returns the range which covers the whole of the document, but excludes
-     * any headers and footers.
-     */
-    public abstract Range getRange();
-
-    /**
-     * Returns the range that covers all text in the file, including main text,
-     * footnotes, headers and comments
-     */
-    public abstract Range getOverallRange();
-
-    /**
-     * Returns document text, i.e. text information from all text pieces,
-     * including OLE descriptions and field codes
-     */
-    public String getDocumentText() {
-        return getText().toString();
-    }
-
-    /**
-     * Internal method to access document text
-     */
-    @Internal
-    public abstract StringBuilder getText();
-
-    public CHPBinTable getCharacterTable() {
-        return _cbt;
-    }
-
-    public PAPBinTable getParagraphTable() {
-        return _pbt;
-    }
-
-    public SectionTable getSectionTable() {
-        return _st;
-    }
-
-    public StyleSheet getStyleSheet() {
-        return _ss;
-    }
-
-    public ListTables getListTables() {
-        return _lt;
-    }
-
-    public FontTable getFontTable() {
-        return _ft;
-    }
-
-    public FileInformationBlock getFileInformationBlock() {
-        return _fib;
-    }
-
-    public ObjectsPool getObjectsPool() {
-        return _objectPool;
-    }
-
-    public abstract TextPieceTable getTextTable();
-
-    @Internal
-    public byte[] getMainStream() {
-        return _mainStream;
-    }
 }
\ No newline at end of file
index 4ab383f9b3e55949f8b6f95eb4c92392e20c8422..f8aa03a1deebef8e8bbb8dfafd6d6a0ca90b5a72 100644 (file)
@@ -31,9 +31,9 @@ public final class HWPFFileSystem
 
   public HWPFFileSystem()
   {
-    _streams.put("WordDocument", new ByteArrayOutputStream());
-    _streams.put("1Table", new ByteArrayOutputStream());
-    _streams.put("Data", new ByteArrayOutputStream());
+    _streams.put("WordDocument", new ByteArrayOutputStream(100000));
+    _streams.put("1Table", new ByteArrayOutputStream(100000));
+    _streams.put("Data", new ByteArrayOutputStream(100000));
   }
 
   public ByteArrayOutputStream getStream(String name)
diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/HWPFTestEncryption.java b/src/scratchpad/testcases/org/apache/poi/hwpf/HWPFTestEncryption.java
deleted file mode 100644 (file)
index 875fb9e..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-/* ====================================================================
-   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<String[]> 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();
-    }
-}