#61162 - En-/decryption support for HWPF

- 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
This commit is contained in:
Andreas Beeker 2017-06-14 17:21:50 +00:00
parent aca91b7888
commit 87591edbf3
16 changed files with 759 additions and 561 deletions

View File

@ -463,6 +463,7 @@ under the License.
<exclude name="**/TestExtractorFactory.java"/> <exclude name="**/TestExtractorFactory.java"/>
<exclude name="**/OutlookTextExtactor.java"/> <exclude name="**/OutlookTextExtactor.java"/>
<exclude name="**/TestEmbedOLEPackage.java"/> <exclude name="**/TestEmbedOLEPackage.java"/>
<exclude name="**/TestHxxFEncryption.java"/>
</patternset> </patternset>
<!-- Prints POI's Ant usage help --> <!-- Prints POI's Ant usage help -->

View File

@ -24,6 +24,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.util.List; import java.util.List;
import org.apache.poi.hpsf.DocumentSummaryInformation; 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.PropertySetFactory;
import org.apache.poi.hpsf.SummaryInformation; import org.apache.poi.hpsf.SummaryInformation;
import org.apache.poi.poifs.crypt.EncryptionInfo; 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.CryptoAPIDecryptor;
import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptor;
import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.DocumentInputStream; import org.apache.poi.poifs.filesystem.DocumentInputStream;
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem; import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
import org.apache.poi.poifs.filesystem.OPOIFSFileSystem; import org.apache.poi.poifs.filesystem.OPOIFSFileSystem;
import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.Internal; import org.apache.poi.util.Internal;
import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger; 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) */ /* Have the property streams been read yet? (Only done on-demand) */
private boolean initialized; private boolean initialized;
private static final String[] encryptedStreamNames = { "EncryptedSummary" };
/** /**
* Constructs a POIDocument with the given directory node. * 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. * if it could not be read for this document.
*/ */
public DocumentSummaryInformation getDocumentSummaryInformation() { public DocumentSummaryInformation getDocumentSummaryInformation() {
if(!initialized) readProperties(); if(!initialized) {
readProperties();
}
return dsInf; return dsInf;
} }
@ -114,7 +118,9 @@ public abstract class POIDocument implements Closeable {
* if it could not be read for this document. * if it could not be read for this document.
*/ */
public SummaryInformation getSummaryInformation() { public SummaryInformation getSummaryInformation() {
if(!initialized) readProperties(); if(!initialized) {
readProperties();
}
return sInf; return sInf;
} }
@ -128,7 +134,9 @@ public abstract class POIDocument implements Closeable {
* then nothing will happen. * then nothing will happen.
*/ */
public void createInformationProperties() { public void createInformationProperties() {
if (!initialized) readProperties(); if (!initialized) {
readProperties();
}
if (sInf == null) { if (sInf == null) {
sInf = PropertySetFactory.newSummaryInformation(); sInf = PropertySetFactory.newSummaryInformation();
} }
@ -144,32 +152,40 @@ public abstract class POIDocument implements Closeable {
* it will remain null; * it will remain null;
*/ */
protected void readProperties() { protected void readProperties() {
PropertySet ps; if (initialized) {
return;
// 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");
} }
DocumentSummaryInformation dsi = readPropertySet(DocumentSummaryInformation.class, DocumentSummaryInformation.DEFAULT_STREAM_NAME);
// SummaryInformation if (dsi != null) {
ps = getPropertySet(SummaryInformation.DEFAULT_STREAM_NAME); dsInf = dsi;
if (ps instanceof SummaryInformation) { }
sInf = (SummaryInformation)ps; SummaryInformation si = readPropertySet(SummaryInformation.class, SummaryInformation.DEFAULT_STREAM_NAME);
} else if (ps != null) { if (si != null) {
logger.log(POILogger.WARN, "SummaryInformation property set came back with wrong class - ", ps.getClass()); sInf = si;
} else {
logger.log(POILogger.WARN, "SummaryInformation property set came back as null");
} }
// Mark the fact that we've now loaded up the properties // Mark the fact that we've now loaded up the properties
initialized = true; 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 * For a given named property entry, either return it or null if
* if it wasn't found * if it wasn't found
@ -177,8 +193,8 @@ public abstract class POIDocument implements Closeable {
* @param setName The property to read * @param setName The property to read
* @return The value of the given property or null if it wasn't found. * @return The value of the given property or null if it wasn't found.
*/ */
protected PropertySet getPropertySet(String setName) { protected PropertySet getPropertySet(String setName) throws IOException {
return getPropertySet(setName, null); return getPropertySet(setName, getEncryptionInfo());
} }
/** /**
@ -189,7 +205,7 @@ public abstract class POIDocument implements Closeable {
* @param encryptionInfo the encryption descriptor in case of cryptoAPI encryption * @param encryptionInfo the encryption descriptor in case of cryptoAPI encryption
* @return The value of the given property or null if it wasn't found. * @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; DirectoryNode dirNode = directory;
NPOIFSFileSystem encPoifs = null; NPOIFSFileSystem encPoifs = null;
@ -197,14 +213,9 @@ public abstract class POIDocument implements Closeable {
try { try {
if (encryptionInfo != null && encryptionInfo.isDocPropsEncrypted()) { if (encryptionInfo != null && encryptionInfo.isDocPropsEncrypted()) {
step = "getting encrypted"; step = "getting encrypted";
String encryptedStream = null; String encryptedStream = getEncryptedPropertyStreamName();
for (String s : encryptedStreamNames) { if (!dirNode.hasEntry(encryptedStream)) {
if (dirNode.hasEntry(s)) { throw new EncryptedDocumentException("can't find encrypted property stream '"+encryptedStream+"'");
encryptedStream = s;
}
}
if (encryptedStream == null) {
throw new EncryptedDocumentException("can't find matching encrypted property stream");
} }
CryptoAPIDecryptor dec = (CryptoAPIDecryptor)encryptionInfo.getDecryptor(); CryptoAPIDecryptor dec = (CryptoAPIDecryptor)encryptionInfo.getDecryptor();
encPoifs = dec.getSummaryEntries(dirNode, encryptedStream); encPoifs = dec.getSummaryEntries(dirNode, encryptedStream);
@ -226,17 +237,12 @@ public abstract class POIDocument implements Closeable {
} finally { } finally {
dis.close(); dis.close();
} }
} catch (IOException e) {
throw e;
} catch (Exception e) { } catch (Exception e) {
logger.log(POILogger.WARN, "Error "+step+" property set with name " + setName, e); throw new IOException("Error "+step+" property set with name " + setName, e);
return null;
} finally { } finally {
if (encPoifs != null) { IOUtils.closeQuietly(encPoifs);
try {
encPoifs.close();
} catch(IOException e) {
logger.log(POILogger.WARN, "Error closing encrypted property poifs", e);
}
}
} }
} }
@ -271,20 +277,48 @@ public abstract class POIDocument implements Closeable {
* {@link NPOIFSFileSystem} occurs * {@link NPOIFSFileSystem} occurs
*/ */
protected void writeProperties(NPOIFSFileSystem outFS, List<String> writtenEntries) throws IOException { 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(); SummaryInformation si = getSummaryInformation();
if (si != null) { if (si != null) {
writePropertySet(SummaryInformation.DEFAULT_STREAM_NAME, si, outFS); writePropertySet(SummaryInformation.DEFAULT_STREAM_NAME, si, fs);
if(writtenEntries != null) { if(writtenEntries != null) {
writtenEntries.add(SummaryInformation.DEFAULT_STREAM_NAME); writtenEntries.add(SummaryInformation.DEFAULT_STREAM_NAME);
} }
} }
DocumentSummaryInformation dsi = getDocumentSummaryInformation(); DocumentSummaryInformation dsi = getDocumentSummaryInformation();
if (dsi != null) { if (dsi != null) {
writePropertySet(DocumentSummaryInformation.DEFAULT_STREAM_NAME, dsi, outFS); writePropertySet(DocumentSummaryInformation.DEFAULT_STREAM_NAME, dsi, fs);
if(writtenEntries != null) { if(writtenEntries != null) {
writtenEntries.add(DocumentSummaryInformation.DEFAULT_STREAM_NAME); 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; directory = newDirectory;
return dn; 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;
}
} }

View File

@ -27,7 +27,7 @@ import org.apache.poi.poifs.filesystem.DirectoryEntry;
* You will typically find the implementation of * You will typically find the implementation of
* a given format's text extractor under * a given format's text extractor under
* org.apache.poi.[format].extractor . * org.apache.poi.[format].extractor .
* *
* @see org.apache.poi.hssf.extractor.ExcelExtractor * @see org.apache.poi.hssf.extractor.ExcelExtractor
* @see org.apache.poi.hslf.extractor.PowerPointExtractor * @see org.apache.poi.hslf.extractor.PowerPointExtractor
* @see org.apache.poi.hdgf.extractor.VisioTextExtractor * @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 * Creates a new text extractor for the given document
* *
* @param document The POIDocument to use in this extractor. * @param document The POIDocument to use in this extractor.
*/ */
public POIOLE2TextExtractor(POIDocument document) { public POIOLE2TextExtractor(POIDocument document) {
this.document = document; this.document = document;
// Ensure any underlying resources, such as open files, // Ensure any underlying resources, such as open files,
// will get cleaned up if the user calls #close() // will get cleaned up if the user calls #close()
setFilesystem(document); setFilesystem(document);
@ -54,17 +54,17 @@ public abstract class POIOLE2TextExtractor extends POITextExtractor {
* Creates a new text extractor, using the same * Creates a new text extractor, using the same
* document as another text extractor. Normally * document as another text extractor. Normally
* only used by properties extractors. * only used by properties extractors.
* *
* @param otherExtractor the extractor which document to be used * @param otherExtractor the extractor which document to be used
*/ */
protected POIOLE2TextExtractor(POIOLE2TextExtractor otherExtractor) { protected POIOLE2TextExtractor(POIOLE2TextExtractor otherExtractor) {
this.document = otherExtractor.document; this.document = otherExtractor.document;
} }
/** /**
* Returns the document information metadata for the 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. * if it could not be read for this document.
*/ */
public DocumentSummaryInformation getDocSummaryInformation() { public DocumentSummaryInformation getDocSummaryInformation() {
@ -72,7 +72,7 @@ public abstract class POIOLE2TextExtractor extends POITextExtractor {
} }
/** /**
* Returns the summary information metadata for the document. * Returns the summary information metadata for the document.
* *
* @return The Summary information for the document or null * @return The Summary information for the document or null
* if it could not be read for this document. * 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 * Returns an HPSF powered text extractor for the
* document properties metadata, such as title and author. * document properties metadata, such as title and author.
* *
* @return an instance of POIExtractor that can extract meta-data. * @return an instance of POIExtractor that can extract meta-data.
*/ */
@Override @Override
@ -96,8 +96,16 @@ public abstract class POIOLE2TextExtractor extends POITextExtractor {
* *
* @return the DirectoryEntry that is associated with the POIDocument of this extractor. * @return the DirectoryEntry that is associated with the POIDocument of this extractor.
*/ */
public DirectoryEntry getRoot() public DirectoryEntry getRoot() {
{
return document.getDirectory(); return document.getDirectory();
} }
}
/**
* Return the underlying POIDocument
*
* @return the underlying POIDocument
*/
public POIDocument getDocument() {
return document;
}
}

View File

@ -20,16 +20,12 @@ package org.apache.poi.hssf.model;
import static org.apache.poi.util.POILogger.DEBUG; import static org.apache.poi.util.POILogger.DEBUG;
import java.security.AccessControlException; import java.security.AccessControlException;
import java.security.GeneralSecurityException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; 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.EscherBSERecord;
import org.apache.poi.ddf.EscherBoolProperty; import org.apache.poi.ddf.EscherBoolProperty;
import org.apache.poi.ddf.EscherContainerRecord; 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.ExtSSTRecord;
import org.apache.poi.hssf.record.ExtendedFormatRecord; import org.apache.poi.hssf.record.ExtendedFormatRecord;
import org.apache.poi.hssf.record.ExternSheetRecord; 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.FileSharingRecord;
import org.apache.poi.hssf.record.FnGroupCountRecord; import org.apache.poi.hssf.record.FnGroupCountRecord;
import org.apache.poi.hssf.record.FontRecord; 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.WriteAccessRecord;
import org.apache.poi.hssf.record.WriteProtectRecord; import org.apache.poi.hssf.record.WriteProtectRecord;
import org.apache.poi.hssf.record.common.UnicodeString; 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.hssf.util.HSSFColor.HSSFColorPredefined;
import org.apache.poi.poifs.crypt.CryptoFunctions; 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.ExternalName;
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet; import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheetRange; import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheetRange;
@ -1048,7 +1038,7 @@ public final class InternalWorkbook {
SSTRecord lSST = null; SSTRecord lSST = null;
int sstPos = 0; int sstPos = 0;
boolean wroteBoundSheets = false; boolean wroteBoundSheets = false;
for ( Record record : records ) { for ( Record record : records.getRecords() ) {
int len = 0; int len = 0;
if (record instanceof SSTRecord) { if (record instanceof SSTRecord) {
lSST = (SSTRecord)record; 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. * Include in it ant code that modifies the workbook record stream and affects its size.
*/ */
public void preSerialize(){ public void preSerialize(){
updateEncryptionRecord();
// Ensure we have enough tab IDs // Ensure we have enough tab IDs
// Can be a few short if new sheets were added // Can be a few short if new sheets were added
if(records.getTabpos() > 0) { 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() { public int getSize() {
int retval = 0; int retval = 0;
SSTRecord lSST = null; SSTRecord lSST = null;
for ( Record record : records ) { for ( Record record : records.getRecords() ) {
if (record instanceof SSTRecord) { if (record instanceof SSTRecord) {
lSST = (SSTRecord)record; lSST = (SSTRecord)record;
} }
@ -1803,7 +1757,7 @@ public final class InternalWorkbook {
* @return the matching record or {@code null} if it wasn't found * @return the matching record or {@code null} if it wasn't found
*/ */
public Record findFirstRecordBySid(short sid) { public Record findFirstRecordBySid(short sid) {
for (Record record : records) { for (Record record : records.getRecords() ) {
if (record.getSid() == sid) { if (record.getSid() == sid) {
return record; return record;
} }
@ -1818,7 +1772,7 @@ public final class InternalWorkbook {
*/ */
public int findFirstRecordLocBySid(short sid) { public int findFirstRecordLocBySid(short sid) {
int index = 0; int index = 0;
for (Record record : records) { for (Record record : records.getRecords() ) {
if (record.getSid() == sid) { if (record.getSid() == sid) {
return index; return index;
} }
@ -1837,7 +1791,7 @@ public final class InternalWorkbook {
*/ */
public Record findNextRecordBySid(short sid, int pos) { public Record findNextRecordBySid(short sid, int pos) {
int matches = 0; int matches = 0;
for (Record record : records) { for (Record record : records.getRecords() ) {
if (record.getSid() == sid && matches++ == pos) { if (record.getSid() == sid && matches++ == pos) {
return record; return record;
} }
@ -1901,7 +1855,7 @@ public final class InternalWorkbook {
} }
// Need to find a DrawingGroupRecord that contains a EscherDggRecord // Need to find a DrawingGroupRecord that contains a EscherDggRecord
for(Record r : records) { for(Record r : records.getRecords() ) {
if (!(r instanceof DrawingGroupRecord)) { if (!(r instanceof DrawingGroupRecord)) {
continue; continue;
} }
@ -2301,4 +2255,12 @@ public final class InternalWorkbook {
public boolean changeExternalReference(String oldUrl, String newUrl) { public boolean changeExternalReference(String oldUrl, String newUrl) {
return linkTable.changeExternalReference(oldUrl, newUrl); return linkTable.changeExternalReference(oldUrl, newUrl);
} }
/**
* Only for internal calls - code based on this is not supported ...
*/
@Internal
public WorkbookRecordList getWorkbookRecordList() {
return records;
}
} }

View File

@ -522,9 +522,7 @@ final class LinkTable {
*/ */
private int findFirstRecordLocBySid(short sid) { private int findFirstRecordLocBySid(short sid) {
int index = 0; int index = 0;
for (Iterator<Record> iterator = _workbookRecordList.iterator(); iterator.hasNext(); ) { for (Record record : _workbookRecordList.getRecords()) {
Record record = iterator.next();
if (record.getSid() == sid) { if (record.getSid() == sid) {
return index; return index;
} }
@ -639,11 +637,11 @@ final class LinkTable {
int supLinkIndex = 0; int supLinkIndex = 0;
// find the posistion of the Add-In SupBookRecord in the workbook stream, // find the posistion of the Add-In SupBookRecord in the workbook stream,
// the created ExternalNameRecord will be appended to it // the created ExternalNameRecord will be appended to it
for (Iterator<Record> iterator = _workbookRecordList.iterator(); iterator.hasNext(); supLinkIndex++) { for (Record record : _workbookRecordList.getRecords()) {
Record record = iterator.next(); if (record instanceof SupBookRecord && ((SupBookRecord) record).isAddInFunctions()) {
if (record instanceof SupBookRecord) { break;
if (((SupBookRecord) record).isAddInFunctions()) break;
} }
supLinkIndex++;
} }
int numberOfNames = extBlock.getNumberOfNames(); int numberOfNames = extBlock.getNumberOfNames();
// a new name is inserted in the end of the SupBookRecord, after the last name // a new name is inserted in the end of the SupBookRecord, after the last name

View File

@ -18,28 +18,37 @@
package org.apache.poi.hssf.model; package org.apache.poi.hssf.model;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
import org.apache.poi.hssf.record.Record; 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 List<Record> records = new ArrayList<Record>();
private int protpos = 0; // holds the position of the protect record. /** holds the position of the protect record */
private int bspos = 0; // holds the position of the last bound sheet. private int protpos = 0;
private int tabpos = 0; // holds the position of the tabid record /** holds the position of the last bound sheet */
private int fontpos = 0; // hold the position of the last font record private int bspos = 0;
private int xfpos = 0; // hold the position of the last extended font record /** holds the position of the tabid record */
private int backuppos = 0; // holds the position of the backup record. private int tabpos = 0;
private int namepos = 0; // holds the position of last name record /** hold the position of the last font record */
private int supbookpos = 0; // holds the position of sup book private int fontpos = 0;
private int externsheetPos = 0;// holds the position of the extern sheet /** hold the position of the last extended font record */
private int palettepos = -1; // hold the position of the palette, if applicable 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) { public void setRecords(List<Record> records) {
this.records = records; this.records = records;
} }
public int size() { public int size() {
@ -52,26 +61,13 @@ public final class WorkbookRecordList implements Iterable<Record> {
public void add(int pos, Record r) { public void add(int pos, Record r) {
records.add(pos, r); records.add(pos, r);
if (getProtpos() >= pos) setProtpos( protpos + 1 ); updateRecordPos(pos, true);
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);
} }
public List<Record> getRecords() { public List<Record> getRecords() {
return records; return records;
} }
public Iterator<Record> iterator() {
return records.iterator();
}
/** /**
* Find the given record in the record list by identity and removes it * 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); records.remove(pos);
if (getProtpos() >= pos) setProtpos( protpos - 1 ); updateRecordPos(pos, false);
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);
} }
public int getProtpos() { public int getProtpos() {
@ -160,7 +146,7 @@ public final class WorkbookRecordList implements Iterable<Record> {
this.palettepos = palettepos; this.palettepos = palettepos;
} }
/** /**
* Returns the namepos. * Returns the namepos.
* @return int * @return int
@ -208,4 +194,48 @@ public final class WorkbookRecordList implements Iterable<Record> {
public void setExternsheetPos(int externsheetPos) { public void setExternsheetPos(int externsheetPos) {
this.externsheetPos = 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 );
}
}
} }

View File

@ -30,6 +30,7 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; 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.EscherMetafileBlip;
import org.apache.poi.ddf.EscherRecord; import org.apache.poi.ddf.EscherRecord;
import org.apache.poi.hpsf.ClassID; 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.OldExcelFormatException;
import org.apache.poi.hssf.model.DrawingManager2; import org.apache.poi.hssf.model.DrawingManager2;
import org.apache.poi.hssf.model.HSSFFormulaParser; 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.InternalSheet.UnsupportedBOFType;
import org.apache.poi.hssf.model.InternalWorkbook; import org.apache.poi.hssf.model.InternalWorkbook;
import org.apache.poi.hssf.model.RecordStream; 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.AbstractEscherHolderRecord;
import org.apache.poi.hssf.record.BackupRecord; import org.apache.poi.hssf.record.BackupRecord;
import org.apache.poi.hssf.record.BoundSheetRecord; 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.aggregates.RecordAggregate.RecordVisitor;
import org.apache.poi.hssf.record.common.UnicodeString; import org.apache.poi.hssf.record.common.UnicodeString;
import org.apache.poi.hssf.record.crypto.Biff8DecryptingStream; 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.hssf.util.CellReference;
import org.apache.poi.poifs.crypt.ChunkedCipherOutputStream; import org.apache.poi.poifs.crypt.ChunkedCipherOutputStream;
import org.apache.poi.poifs.crypt.Decryptor; 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.crypt.Encryptor;
import org.apache.poi.poifs.filesystem.DirectoryEntry; import org.apache.poi.poifs.filesystem.DirectoryEntry;
import org.apache.poi.poifs.filesystem.DirectoryNode; 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 // Write out our HPFS properties, if we have them
writeProperties(fs, excepts); writeProperties(fs, excepts);
if (preserveNodes) { if (preserveNodes) {
// Don't write out the old Workbook, we'll be doing our new one // 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, // 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 // don't write the old one as we'll use the correct name shortly
excepts.addAll(Arrays.asList(WORKBOOK_DIR_ENTRY_NAMES)); 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 // Copy over all the other nodes to our new poifs
EntryUtils.copyNodes( EntryUtils.copyNodes(
new FilteringDirectoryNode(getDirectory(), excepts) new FilteringDirectoryNode(getDirectory(), excepts)
@ -1520,6 +1536,7 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
HSSFSheet[] sheets = getSheets(); HSSFSheet[] sheets = getSheets();
int nSheets = sheets.length; int nSheets = sheets.length;
updateEncryptionInfo();
// before getting the workbook size we must tell the sheets that // before getting the workbook size we must tell the sheets that
// serialization is about to occur. // serialization is about to occur.
@ -1566,22 +1583,14 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
@SuppressWarnings("resource") @SuppressWarnings("resource")
protected void encryptBytes(byte buf[]) { protected void encryptBytes(byte buf[]) {
int initialOffset = 0; EncryptionInfo ei = getEncryptionInfo();
FilePassRecord fpr = null; if (ei == null) {
for (Record r : workbook.getRecords()) {
initialOffset += r.getRecordSize();
if (r instanceof FilePassRecord) {
fpr = (FilePassRecord)r;
break;
}
}
if (fpr == null) {
return; return;
} }
Encryptor enc = ei.getEncryptor();
int initialOffset = 0;
LittleEndianByteArrayInputStream plain = new LittleEndianByteArrayInputStream(buf, 0); // NOSONAR LittleEndianByteArrayInputStream plain = new LittleEndianByteArrayInputStream(buf, 0); // NOSONAR
LittleEndianByteArrayOutputStream leos = new LittleEndianByteArrayOutputStream(buf, 0); // NOSONAR LittleEndianByteArrayOutputStream leos = new LittleEndianByteArrayOutputStream(buf, 0); // NOSONAR
Encryptor enc = fpr.getEncryptionInfo().getEncryptor();
enc.setChunkSize(Biff8DecryptingStream.RC4_REKEYING_INTERVAL); enc.setChunkSize(Biff8DecryptingStream.RC4_REKEYING_INTERVAL);
byte tmp[] = new byte[1024]; byte tmp[] = new byte[1024];
try { try {
@ -2306,4 +2315,50 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
public SpreadsheetVersion getSpreadsheetVersion() { public SpreadsheetVersion getSpreadsheetVersion() {
return SpreadsheetVersion.EXCEL97; 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);
}
}
}
} }

View File

@ -153,14 +153,19 @@ public class CryptoAPIDecryptor extends Decryptor implements Cloneable {
/** /**
* Decrypt the Document-/SummaryInformation and other optionally streams. * Decrypt the Document-/SummaryInformation and other optionally streams.
* Opposed to other crypto modes, cryptoapi is record based and can't be used * 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> * @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) public POIFSFileSystem getSummaryEntries(DirectoryNode root, String encryptedStream)
throws IOException, GeneralSecurityException { throws IOException, GeneralSecurityException {
// HSLF: encryptedStream
// HSSF: encryption
DocumentNode es = (DocumentNode) root.getEntry(encryptedStream); DocumentNode es = (DocumentNode) root.getEntry(encryptedStream);
DocumentInputStream dis = root.createDocumentInputStream(es); DocumentInputStream dis = root.createDocumentInputStream(es);
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();

View File

@ -32,10 +32,6 @@ import javax.crypto.Cipher;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import org.apache.poi.EncryptedDocumentException; 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.ChunkedCipherOutputStream;
import org.apache.poi.poifs.crypt.CryptoFunctions; import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.poifs.crypt.DataSpaceMapUtils; 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.crypt.standard.EncryptionRecord;
import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.DocumentInputStream; 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.IOUtils;
import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianByteArrayOutputStream; 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> * @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 { throws IOException, GeneralSecurityException {
CryptoAPIDocumentOutputStream bos = new CryptoAPIDocumentOutputStream(this); // NOSONAR CryptoAPIDocumentOutputStream bos = new CryptoAPIDocumentOutputStream(this); // NOSONAR
byte buf[] = new byte[8]; byte buf[] = new byte[8];
bos.write(buf, 0, 8); // skip header bos.write(buf, 0, 8); // skip header
String entryNames[] = {
SummaryInformation.DEFAULT_STREAM_NAME,
DocumentSummaryInformation.DEFAULT_STREAM_NAME
};
List<StreamDescriptorEntry> descList = new ArrayList<StreamDescriptorEntry>(); List<StreamDescriptorEntry> descList = new ArrayList<StreamDescriptorEntry>();
int block = 0; int block = 0;
for (String entryName : entryNames) { for (Entry entry : entries.getRoot()) {
if (!dir.hasEntry(entryName)) { if (entry.isDirectoryEntry()) {
continue; continue;
} }
StreamDescriptorEntry descEntry = new StreamDescriptorEntry(); StreamDescriptorEntry descEntry = new StreamDescriptorEntry();
descEntry.block = block; descEntry.block = block;
descEntry.streamOffset = bos.size(); descEntry.streamOffset = bos.size();
descEntry.streamName = entryName; descEntry.streamName = entry.getName();
descEntry.flags = StreamDescriptorEntry.flagStream.setValue(0, 1); descEntry.flags = StreamDescriptorEntry.flagStream.setValue(0, 1);
descEntry.reserved2 = 0; descEntry.reserved2 = 0;
bos.setBlock(block); bos.setBlock(block);
DocumentInputStream dis = dir.createDocumentInputStream(entryName); DocumentInputStream dis = dir.createDocumentInputStream(entry);
IOUtils.copy(dis, bos); IOUtils.copy(dis, bos);
dis.close(); dis.close();
descEntry.streamSize = bos.size() - descEntry.streamOffset; descEntry.streamSize = bos.size() - descEntry.streamOffset;
descList.add(descEntry); descList.add(descEntry);
dir.getEntry(entryName).delete();
block++; block++;
} }
@ -197,16 +188,7 @@ public class CryptoAPIEncryptor extends Encryptor implements Cloneable {
bos.write(buf, 0, 8); bos.write(buf, 0, 8);
bos.setSize(savedSize); bos.setSize(savedSize);
dir.createDocument("EncryptedSummary", new ByteArrayInputStream(bos.getBuf(), 0, savedSize)); dir.createDocument(encryptedStream, 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;
} }
protected int getKeySizeInBytes() { protected int getKeySizeInBytes() {

View File

@ -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();
}
}

View 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.ChunkedCipherOutputStream;
import org.apache.poi.poifs.crypt.Decryptor; import org.apache.poi.poifs.crypt.Decryptor;
import org.apache.poi.poifs.crypt.EncryptionInfo; import org.apache.poi.poifs.crypt.EncryptionInfo;
import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIDecryptor; import org.apache.poi.poifs.crypt.Encryptor;
import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptor;
import org.apache.poi.util.BitField; import org.apache.poi.util.BitField;
import org.apache.poi.util.IOUtils; import org.apache.poi.util.IOUtils;
import org.apache.poi.util.Internal; import org.apache.poi.util.Internal;
@ -55,8 +54,7 @@ import org.apache.poi.util.LittleEndianByteArrayOutputStream;
@Internal @Internal
public class HSLFSlideShowEncrypted implements Closeable { public class HSLFSlideShowEncrypted implements Closeable {
DocumentEncryptionAtom dea; DocumentEncryptionAtom dea;
CryptoAPIEncryptor enc = null; EncryptionInfo _encryptionInfo;
CryptoAPIDecryptor dec = null;
// Cipher cipher = null; // Cipher cipher = null;
ChunkedCipherOutputStream cyos = null; ChunkedCipherOutputStream cyos = null;
@ -120,11 +118,15 @@ public class HSLFSlideShowEncrypted implements Closeable {
} }
assert(r instanceof DocumentEncryptionAtom); assert(r instanceof DocumentEncryptionAtom);
this.dea = (DocumentEncryptionAtom)r; this.dea = (DocumentEncryptionAtom)r;
decryptInit();
String pass = Biff8EncryptionKey.getCurrentUserPassword(); String pass = Biff8EncryptionKey.getCurrentUserPassword();
if(!dec.verifyPassword(pass != null ? pass : Decryptor.DEFAULT_PASSWORD)) { EncryptionInfo ei = getEncryptionInfo();
throw new EncryptedPowerPointFileException("PowerPoint file is encrypted. The correct password needs to be set via Biff8EncryptionKey.setCurrentUserPassword()"); 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; return dea;
} }
protected void decryptInit() { protected EncryptionInfo getEncryptionInfo() {
if (dec != null) { return (dea != null) ? dea.getEncryptionInfo() : 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 OutputStream encryptRecord(OutputStream plainStream, int persistId, Record record) { protected OutputStream encryptRecord(OutputStream plainStream, int persistId, Record record) {
boolean isPlain = (dea == null boolean isPlain = (dea == null
|| record instanceof UserEditAtom || record instanceof UserEditAtom
@ -166,9 +154,8 @@ public class HSLFSlideShowEncrypted implements Closeable {
return plainStream; return plainStream;
} }
encryptInit();
if (cyos == null) { if (cyos == null) {
Encryptor enc = getEncryptionInfo().getEncryptor();
enc.setChunkSize(-1); enc.setChunkSize(-1);
cyos = enc.getDataStream(plainStream, 0); cyos = enc.getDataStream(plainStream, 0);
} }
@ -190,12 +177,12 @@ public class HSLFSlideShowEncrypted implements Closeable {
return; return;
} }
decryptInit(); Decryptor dec = getEncryptionInfo().getDecryptor();
dec.setChunkSize(-1); dec.setChunkSize(-1);
LittleEndianByteArrayInputStream lei = new LittleEndianByteArrayInputStream(docstream, offset); // NOSONAR LittleEndianByteArrayInputStream lei = new LittleEndianByteArrayInputStream(docstream, offset); // NOSONAR
ChunkedCipherInputStream ccis = null; ChunkedCipherInputStream ccis = null;
try { try {
ccis = dec.getDataStream(lei, docstream.length-offset, 0); ccis = (ChunkedCipherInputStream)dec.getDataStream(lei, docstream.length-offset, 0);
ccis.initCipherForBlock(persistId); ccis.initCipherForBlock(persistId);
// decrypt header and read length to be decrypted // 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 // when reading the picture elements, each time a segment is read, the cipher needs
// to be reset (usually done when calling Cipher.doFinal) // to be reset (usually done when calling Cipher.doFinal)
LittleEndianByteArrayInputStream lei = new LittleEndianByteArrayInputStream(pictstream, offset); 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); readFully(ccis, pictstream, offset, len);
ccis.close(); ccis.close();
lei.close(); lei.close();
@ -228,8 +216,6 @@ public class HSLFSlideShowEncrypted implements Closeable {
return; return;
} }
decryptInit();
try { try {
// decrypt header and read length to be decrypted // decrypt header and read length to be decrypted
decryptPicBytes(pictstream, offset, 8); decryptPicBytes(pictstream, offset, 8);
@ -302,12 +288,11 @@ public class HSLFSlideShowEncrypted implements Closeable {
return; return;
} }
encryptInit();
LittleEndianByteArrayOutputStream los = new LittleEndianByteArrayOutputStream(pictstream, offset); // NOSONAR LittleEndianByteArrayOutputStream los = new LittleEndianByteArrayOutputStream(pictstream, offset); // NOSONAR
ChunkedCipherOutputStream ccos = null; ChunkedCipherOutputStream ccos = null;
try { try {
Encryptor enc = getEncryptionInfo().getEncryptor();
enc.setChunkSize(-1); enc.setChunkSize(-1);
ccos = enc.getDataStream(los, 0); ccos = enc.getDataStream(los, 0);
int recInst = fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset)); int recInst = fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset));
@ -396,11 +381,10 @@ public class HSLFSlideShowEncrypted implements Closeable {
// create password record // create password record
if (dea == null) { if (dea == null) {
dea = new DocumentEncryptionAtom(); dea = new DocumentEncryptionAtom();
enc = null;
} }
encryptInit();
EncryptionInfo ei = dea.getEncryptionInfo(); EncryptionInfo ei = dea.getEncryptionInfo();
byte salt[] = ei.getVerifier().getSalt(); byte salt[] = ei.getVerifier().getSalt();
Encryptor enc = getEncryptionInfo().getEncryptor();
if (salt == null) { if (salt == null) {
enc.confirmPassword(password); enc.confirmPassword(password);
} else { } else {

View File

@ -24,7 +24,6 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -34,7 +33,6 @@ import java.util.NavigableMap;
import java.util.TreeMap; import java.util.TreeMap;
import org.apache.poi.POIDocument; import org.apache.poi.POIDocument;
import org.apache.poi.hpsf.PropertySet;
import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException; import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException;
import org.apache.poi.hslf.exceptions.HSLFException; import org.apache.poi.hslf.exceptions.HSLFException;
import org.apache.poi.hslf.record.CurrentUserAtom; 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.Record;
import org.apache.poi.hslf.record.RecordTypes; import org.apache.poi.hslf.record.RecordTypes;
import org.apache.poi.hslf.record.UserEditAtom; 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.DirectoryNode;
import org.apache.poi.poifs.filesystem.DocumentEntry; import org.apache.poi.poifs.filesystem.DocumentEntry;
import org.apache.poi.poifs.filesystem.DocumentInputStream; 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 @Override
protected PropertySet getPropertySet(String setName) { public EncryptionInfo getEncryptionInfo() throws IOException {
DocumentEncryptionAtom dea = getDocumentEncryptionAtom(); DocumentEncryptionAtom dea = getDocumentEncryptionAtom();
return (dea == null) return (dea != null) ? dea.getEncryptionInfo() : 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);
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);
}
}
}
/* ******************* adding methods follow ********************* */ /* ******************* 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 { private static class BufAccessBAOS extends ByteArrayOutputStream {
public byte[] getBuf() { public byte[] getBuf() {

View File

@ -23,6 +23,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.security.GeneralSecurityException;
import org.apache.poi.hpsf.DocumentSummaryInformation; import org.apache.poi.hpsf.DocumentSummaryInformation;
import org.apache.poi.hpsf.SummaryInformation; 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.OfficeDrawingsImpl;
import org.apache.poi.hwpf.usermodel.Range; import org.apache.poi.hwpf.usermodel.Range;
import org.apache.poi.poifs.common.POIFSConstants; 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.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.DirectoryNode;
import org.apache.poi.poifs.filesystem.Entry; import org.apache.poi.poifs.filesystem.Entry;
import org.apache.poi.poifs.filesystem.EntryUtils; import org.apache.poi.poifs.filesystem.EntryUtils;
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem; import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.util.Internal; 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); _fields = new FieldsImpl(_fieldsTables);
} }
@Override
@Internal @Internal
public TextPieceTable getTextTable() public TextPieceTable getTextTable()
{ {
@ -340,6 +347,7 @@ public final class HWPFDocument extends HWPFDocumentCore {
return _dop; return _dop;
} }
@Override
public Range getOverallRange() { public Range getOverallRange() {
return new Range(0, _text.length(), this); 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 * Returns the range which covers the whole of the document, but excludes
* any headers and footers. * any headers and footers.
*/ */
@Override
public Range getRange() public Range getRange()
{ {
// // First up, trigger a full-recalculate // // First up, trigger a full-recalculate
@ -394,8 +403,9 @@ public final class HWPFDocument extends HWPFDocumentCore {
{ {
int length = getFileInformationBlock() int length = getFileInformationBlock()
.getSubdocumentTextStreamLength( previos ); .getSubdocumentTextStreamLength( previos );
if ( subdocument == previos ) if ( subdocument == previos ) {
return new Range( startCp, startCp + length, this ); return new Range( startCp, startCp + length, this );
}
startCp += length; startCp += length;
} }
throw new UnsupportedOperationException( throw new UnsupportedOperationException(
@ -603,34 +613,52 @@ public final class HWPFDocument extends HWPFDocumentCore {
* @throws IOException If there is an unexpected IOException from the passed * @throws IOException If there is an unexpected IOException from the passed
* in OutputStream. * in OutputStream.
*/ */
@Override
public void write(OutputStream out) throws IOException { public void write(OutputStream out) throws IOException {
NPOIFSFileSystem pfs = new NPOIFSFileSystem(); NPOIFSFileSystem pfs = new NPOIFSFileSystem();
write(pfs, true); write(pfs, true);
pfs.writeFilesystem( out ); 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. // clear the offsets and sizes in our FileInformationBlock.
_fib.clearOffsetsSizes(); _fib.clearOffsetsSizes();
// determine the FileInformationBLock size // determine the FileInformationBLock size
int fibSize = _fib.getSize(); 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 // preserve space for the FileInformationBlock because we will be writing
// it after we write everything else. // it after we write everything else.
byte[] placeHolder = new byte[fibSize]; byte[] placeHolder = new byte[fibSize];
wordDocumentStream.write(placeHolder); wordDocumentStream.write(placeHolder);
int mainOffset = wordDocumentStream.size(); 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. // write out the StyleSheet.
_fib.setFcStshf(tableOffset); _fib.setFcStshf(tableOffset);
_ss.writeTo(tableStream); _ss.writeTo(tableStream);
@ -869,13 +897,7 @@ public final class HWPFDocument extends HWPFDocumentCore {
_fib.setCbMac(wordDocumentStream.size()); _fib.setCbMac(wordDocumentStream.size());
// make sure that the table, doc and data streams use big blocks. // make sure that the table, doc and data streams use big blocks.
byte[] mainBuf = wordDocumentStream.toByteArray(); byte[] mainBuf = fillUp4096(wordDocumentStream);
if (mainBuf.length < 4096)
{
byte[] tempBuf = new byte[4096];
System.arraycopy(mainBuf, 0, tempBuf, 0, mainBuf.length);
mainBuf = tempBuf;
}
// Table1 stream will be used // Table1 stream will be used
_fib.getFibBase().setFWhichTblStm( true ); _fib.getFibBase().setFWhichTblStm( true );
@ -884,97 +906,51 @@ public final class HWPFDocument extends HWPFDocumentCore {
//_fib.serialize(mainBuf, 0); //_fib.serialize(mainBuf, 0);
_fib.writeTo(mainBuf, tableStream); _fib.writeTo(mainBuf, tableStream);
byte[] tableBuf = tableStream.toByteArray(); byte[] tableBuf = fillUp4096(tableStream);
if (tableBuf.length < 4096) byte[] dataBuf = fillUp4096(_dataStream);
{
byte[] tempBuf = new byte[4096]; // Create a new document - ignoring the order of the old entries
System.arraycopy(tableBuf, 0, tempBuf, 0, tableBuf.length); if (ei == null) {
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;
}
// 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;
}
}
else if ( copyOtherEntries )
{
EntryUtils.copyNodeRecursively( entry, pfs.getRoot() );
}
}
if ( !docWritten )
write(pfs, mainBuf, STREAM_WORD_DOCUMENT); write(pfs, mainBuf, STREAM_WORD_DOCUMENT);
if ( !tableWritten )
write(pfs, tableBuf, STREAM_TABLE_1); write(pfs, tableBuf, STREAM_TABLE_1);
if ( !propertiesWritten )
writeProperties( pfs );
if ( !dataWritten )
write(pfs, dataBuf, STREAM_DATA); write(pfs, dataBuf, STREAM_DATA);
if ( !objectPoolWritten && copyOtherEntries ) } else {
_objectPool.writeTo( pfs.getRoot() ); 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();
}
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 );
}
}
}
/* /*
* since we updated all references in FIB and etc, using new arrays to * since we updated all references in FIB and etc, using new arrays to
@ -984,6 +960,43 @@ public final class HWPFDocument extends HWPFDocumentCore {
this._tableStream = tableStream.toByteArray(); this._tableStream = tableStream.toByteArray();
this._dataStream = dataBuf; 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 { private static void write(NPOIFSFileSystem pfs, byte[] data, String name) throws IOException {
pfs.createOrUpdateDocument(new ByteArrayInputStream(data), name); pfs.createOrUpdateDocument(new ByteArrayInputStream(data), name);
} }
@ -1009,9 +1022,8 @@ public final class HWPFDocument extends HWPFDocumentCore {
list.getLFOData() ); list.getLFOData() );
} }
public void delete(int start, int length) public void delete(int start, int length) {
{ Range r = new Range(start, start + length, this);
Range r = new Range(start, start + length, this); r.delete();
r.delete(); }
} }
}

View File

@ -18,7 +18,6 @@
package org.apache.poi.hwpf; package org.apache.poi.hwpf;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.PushbackInputStream; import java.io.PushbackInputStream;
@ -26,7 +25,6 @@ import java.security.GeneralSecurityException;
import org.apache.poi.EncryptedDocumentException; import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.POIDocument; import org.apache.poi.POIDocument;
import org.apache.poi.hpsf.PropertySet;
import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey; import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
import org.apache.poi.hwpf.model.CHPBinTable; import org.apache.poi.hwpf.model.CHPBinTable;
import org.apache.poi.hwpf.model.FibBase; 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.Decryptor;
import org.apache.poi.poifs.crypt.EncryptionInfo; import org.apache.poi.poifs.crypt.EncryptionInfo;
import org.apache.poi.poifs.crypt.EncryptionMode; 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.DirectoryEntry;
import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.DocumentEntry; 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_0 = "0Table";
protected static final String STREAM_TABLE_1 = "1Table"; 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 */ /** Holds OLE2 objects */
protected ObjectPoolImpl _objectPool; protected ObjectPoolImpl _objectPool;
@ -171,110 +180,6 @@ public abstract class HWPFDocumentCore extends POIDocument {
} }
_objectPool = new ObjectPoolImpl(objectPoolEntry); _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);
}
}
/** /**
* Returns the range which covers the whole of the document, but excludes * Returns the range which covers the whole of the document, but excludes
* any headers and footers. * any headers and footers.
@ -339,4 +244,121 @@ public abstract class HWPFDocumentCore extends POIDocument {
public byte[] getMainStream() { public byte[] getMainStream() {
return _mainStream; return _mainStream;
} }
@Override
public EncryptionInfo getEncryptionInfo() throws IOException {
if (_encryptionInfo != null) {
return _encryptionInfo;
}
// Create our FIB, and check for the doc being encrypted
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;
}
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(RC4_REKEYING_INTERVAL);
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;
}
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
* 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);
}
}
} }

View File

@ -31,9 +31,9 @@ public final class HWPFFileSystem
public HWPFFileSystem() public HWPFFileSystem()
{ {
_streams.put("WordDocument", new ByteArrayOutputStream()); _streams.put("WordDocument", new ByteArrayOutputStream(100000));
_streams.put("1Table", new ByteArrayOutputStream()); _streams.put("1Table", new ByteArrayOutputStream(100000));
_streams.put("Data", new ByteArrayOutputStream()); _streams.put("Data", new ByteArrayOutputStream(100000));
} }
public ByteArrayOutputStream getStream(String name) public ByteArrayOutputStream getStream(String name)

View File

@ -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();
}
}