You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@poi.apache.org by ki...@apache.org on 2016/09/28 23:36:09 UTC
svn commit: r1762726 [1/3] - in /poi: site/src/documentation/content/xdocs/
trunk/ trunk/src/java/org/apache/poi/
trunk/src/java/org/apache/poi/hssf/model/
trunk/src/java/org/apache/poi/hssf/record/
trunk/src/java/org/apache/poi/hssf/record/cont/ trunk...
Author: kiwiwings
Date: Wed Sep 28 23:36:09 2016
New Revision: 1762726
URL: http://svn.apache.org/viewvc?rev=1762726&view=rev
Log:
Bug 59857 - Password protected files with "Microsoft Enhanced Cryptographic Provider v1.0"
Added:
poi/trunk/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentInputStream.java
- copied unchanged from r1762715, poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentInputStream.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentOutputStream.java
- copied unchanged from r1762715, poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentOutputStream.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/xor/
- copied from r1762715, poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/
poi/trunk/src/testcases/org/apache/poi/hssf/usermodel/TestCryptoAPI.java
- copied unchanged from r1762715, poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/usermodel/TestCryptoAPI.java
poi/trunk/src/testcases/org/apache/poi/poifs/crypt/AllEncryptionTests.java
- copied unchanged from r1762715, poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/AllEncryptionTests.java
poi/trunk/src/testcases/org/apache/poi/poifs/crypt/TestBiff8DecryptingStream.java
- copied unchanged from r1762715, poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/TestBiff8DecryptingStream.java
poi/trunk/src/testcases/org/apache/poi/poifs/crypt/TestXorEncryption.java
- copied unchanged from r1762715, poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/TestXorEncryption.java
poi/trunk/src/testcases/org/apache/poi/poifs/crypt/binaryrc4/
- copied from r1762715, poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/binaryrc4/
poi/trunk/test-data/slideshow/60003.ppt
- copied unchanged from r1762715, poi/branches/hssf_cryptoapi/test-data/slideshow/60003.ppt
Removed:
poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8Cipher.java
poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8RC4.java
poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8RC4Key.java
poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8XOR.java
poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8XORKey.java
poi/trunk/src/testcases/org/apache/poi/hssf/record/crypto/
Modified:
poi/site/src/documentation/content/xdocs/encryption.xml
poi/site/src/documentation/content/xdocs/status.xml
poi/trunk/ (props changed)
poi/trunk/src/java/org/apache/poi/POIDocument.java
poi/trunk/src/java/org/apache/poi/hssf/model/InternalWorkbook.java
poi/trunk/src/java/org/apache/poi/hssf/record/BoundSheetRecord.java
poi/trunk/src/java/org/apache/poi/hssf/record/FilePassRecord.java
poi/trunk/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java
poi/trunk/src/java/org/apache/poi/hssf/record/RecordInputStream.java
poi/trunk/src/java/org/apache/poi/hssf/record/cont/ContinuableRecordInput.java
poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8DecryptingStream.java
poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.java
poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/Decryptor.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionHeader.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionInfoBuilder.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionMode.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionVerifier.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/Encryptor.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Decryptor.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionHeader.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionInfoBuilder.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionVerifier.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Encryptor.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDecryptor.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionHeader.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionInfoBuilder.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionVerifier.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptor.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardDecryptor.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionHeader.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionInfoBuilder.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionVerifier.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptor.java
poi/trunk/src/java/org/apache/poi/poifs/filesystem/DocumentInputStream.java
poi/trunk/src/java/org/apache/poi/util/LittleEndianByteArrayInputStream.java
poi/trunk/src/java/org/apache/poi/util/LittleEndianByteArrayOutputStream.java
poi/trunk/src/java/org/apache/poi/util/LittleEndianInput.java
poi/trunk/src/java/org/apache/poi/util/LittleEndianInputStream.java
poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java
poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionHeader.java
poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionInfoBuilder.java
poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionVerifier.java
poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptor.java
poi/trunk/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestAgileEncryptionParameters.java
poi/trunk/src/scratchpad/src/org/apache/poi/hslf/record/DocumentEncryptionAtom.java
poi/trunk/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowEncrypted.java
poi/trunk/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowImpl.java
poi/trunk/src/scratchpad/testcases/org/apache/poi/hslf/record/TestDocumentEncryption.java
poi/trunk/src/testcases/org/apache/poi/hssf/record/AllRecordTests.java
poi/trunk/src/testcases/org/apache/poi/hssf/record/TestRecordFactoryInputStream.java
poi/trunk/src/testcases/org/apache/poi/poifs/crypt/TestCipherAlgorithm.java
Modified: poi/site/src/documentation/content/xdocs/encryption.xml
URL: http://svn.apache.org/viewvc/poi/site/src/documentation/content/xdocs/encryption.xml?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/site/src/documentation/content/xdocs/encryption.xml (original)
+++ poi/site/src/documentation/content/xdocs/encryption.xml Wed Sep 28 23:36:09 2016
@@ -74,8 +74,8 @@
<td>XWPF</td>
</tr>
<tr>
- <td><link href="https://msdn.microsoft.com/en-us/library/dd949802(v=office.12).aspx">XOR obfuscation</link></td>
- <td class="feature-partly">Read</td>
+ <td><link href="https://msdn.microsoft.com/en-us/library/dd949802(v=office.12).aspx">XOR obfuscation *)</link></td>
+ <td class="feature-yes">Yes</td>
<td class="feature-na">N/A</td>
<td class="feature-no">No</td>
<td class="feature-na">N/A</td>
@@ -84,7 +84,7 @@
</tr>
<tr>
<td><link href="https://msdn.microsoft.com/en-us/library/dd909583(v=office.12).aspx">40-bit RC4 encryption</link></td>
- <td class="feature-partly">Read</td>
+ <td class="feature-yes">Yes</td>
<td class="feature-na">N/A</td>
<td class="feature-no">No</td>
<td class="feature-na">N/A</td>
@@ -93,7 +93,7 @@
</tr>
<tr>
<td><link href="https://msdn.microsoft.com/en-us/library/dd910113(v=office.12).aspx">Office Binary Document RC4 CryptoAPI Encryption</link></td>
- <td class="feature-no">No</td>
+ <td class="feature-yes">Yes</td>
<td class="feature-yes">Yes</td>
<td class="feature-no">No</td>
<td class="feature-na">N/A</td>
@@ -101,7 +101,7 @@
<td class="feature-na">N/A</td>
</tr>
<tr>
- <td><link href="https://msdn.microsoft.com/en-us/library/dd907466(v=office.12).aspx">Office Binary Document RC4 Encryption *)</link></td>
+ <td><link href="https://msdn.microsoft.com/en-us/library/dd907466(v=office.12).aspx">Office Binary Document RC4 Encryption **)</link></td>
<td class="feature-na">N/A</td>
<td class="feature-na">N/A</td>
<td class="feature-na">N/A</td>
@@ -138,7 +138,10 @@
</tr>
</table>
- <p>*) the <link href="https://msdn.microsoft.com/en-us/library/cc313071(v=office.12).aspx">MS-OFFCRYPTO</link>
+ <p>*) the xor encryption is flawed and works only for very small files - see <link href="https://bz.apache.org/bugzilla/show_bug.cgi?id=59857">#59857</link>.
+ </p>
+
+ <p>**) the <link href="https://msdn.microsoft.com/en-us/library/cc313071(v=office.12).aspx">MS-OFFCRYPTO</link>
documentation only mentions the RC4 (without CryptoAPI) encryption as a "in place" encryption, but
apparently there's also a container based method with that key generation logic.
</p>
Modified: poi/site/src/documentation/content/xdocs/status.xml
URL: http://svn.apache.org/viewvc/poi/site/src/documentation/content/xdocs/status.xml?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/site/src/documentation/content/xdocs/status.xml (original)
+++ poi/site/src/documentation/content/xdocs/status.xml Wed Sep 28 23:36:09 2016
@@ -40,6 +40,7 @@
</devs>
<release version="3.16-beta1" date="2016-11-??">
+ <action dev="PD" type="fix" fixes-bug="59857">Password protected files with "Microsoft Enhanced Cryptographic Provider v1.0"</action>
<action dev="PD" type="fix" fixes-bug="59687">XSSF: Comments removed from wrong row when removing a row from a sheet with empty rows</action>
<action dev="PD" type="fix" fixes-bug="59933">POI: Fix IllegalAccess exception caused by logger</action>
<action dev="PD" type="add" fixes-bug="59853">XSSF: Support Table (structured reference) sources in PivotTables</action>
Propchange: poi/trunk/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Wed Sep 28 23:36:09 2016
@@ -1,6 +1,7 @@
/poi/branches/common_sl:1661320-1691849
/poi/branches/excelant:1069732-1073692
/poi/branches/gsoc2012:1341450-1371650
+/poi/branches/hssf_cryptoapi:1753906-1762715
/poi/branches/ss_border_property_template:1747847-1748074
/poi/branches/xml_signature:1617139-1628347
/poi/branches/xssf_structured_references:1747607-1747656
Modified: poi/trunk/src/java/org/apache/poi/POIDocument.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/POIDocument.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/POIDocument.java (original)
+++ poi/trunk/src/java/org/apache/poi/POIDocument.java Wed Sep 28 23:36:09 2016
@@ -32,6 +32,7 @@ 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.cryptoapi.CryptoAPIDecryptor;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.DocumentInputStream;
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
@@ -59,6 +60,8 @@ public abstract class POIDocument implem
/* Have the property streams been read yet? (Only done on-demand) */
private boolean initialized = false;
+
+ private static final String[] encryptedStreamNames = { "EncryptedSummary" };
/**
* Constructs a POIDocument with the given directory node.
@@ -195,13 +198,18 @@ public abstract class POIDocument implem
try {
if (encryptionInfo != null) {
step = "getting encrypted";
- InputStream is = encryptionInfo.getDecryptor().getDataStream(directory);
- try {
- encPoifs = new NPOIFSFileSystem(is);
- dirNode = encPoifs.getRoot();
- } finally {
- is.close();
+ 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");
}
+ CryptoAPIDecryptor dec = (CryptoAPIDecryptor)encryptionInfo.getDecryptor();
+ encPoifs = dec.getSummaryEntries(dirNode, encryptedStream);
+ dirNode = encPoifs.getRoot();
}
//directory can be null when creating new documents
Modified: poi/trunk/src/java/org/apache/poi/hssf/model/InternalWorkbook.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/model/InternalWorkbook.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/model/InternalWorkbook.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/model/InternalWorkbook.java Wed Sep 28 23:36:09 2016
@@ -18,6 +18,7 @@
package org.apache.poi.hssf.model;
import java.security.AccessControlException;
+import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -25,6 +26,9 @@ 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;
@@ -52,6 +56,7 @@ import org.apache.poi.hssf.record.Escher
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;
@@ -82,8 +87,13 @@ import org.apache.poi.hssf.record.Window
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;
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;
@@ -1082,10 +1092,8 @@ public final class InternalWorkbook {
SSTRecord sst = null;
int sstPos = 0;
boolean wroteBoundSheets = false;
- for ( int k = 0; k < records.size(); k++ )
- {
+ for ( Record record : records ) {
- Record record = records.get( k );
int len = 0;
if (record instanceof SSTRecord)
{
@@ -1124,6 +1132,8 @@ 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) {
@@ -1134,6 +1144,49 @@ public final class InternalWorkbook {
}
}
+ private void updateEncryptionRecord() {
+ FilePassRecord fpr = null;
+ int fprPos = -1;
+ for (Record r : records.getRecords()) {
+ fprPos++;
+ if (r instanceof FilePassRecord) {
+ fpr = (FilePassRecord)r;
+ break;
+ }
+ }
+
+ String password = Biff8EncryptionKey.getCurrentUserPassword();
+ if (password == null) {
+ if (fpr != null) {
+ // need to remove password data
+ records.remove(fprPos);
+ }
+ return;
+ } 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;
Modified: poi/trunk/src/java/org/apache/poi/hssf/record/BoundSheetRecord.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/BoundSheetRecord.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/BoundSheetRecord.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/BoundSheetRecord.java Wed Sep 28 23:36:09 2016
@@ -24,6 +24,8 @@ import java.util.List;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianOutput;
import org.apache.poi.util.StringUtil;
import org.apache.poi.ss.util.WorkbookUtil;
@@ -60,7 +62,9 @@ public final class BoundSheetRecord exte
* @param in the record stream to read from
*/
public BoundSheetRecord(RecordInputStream in) {
- field_1_position_of_BOF = in.readInt();
+ byte buf[] = new byte[LittleEndianConsts.INT_SIZE];
+ in.readPlain(buf, 0, buf.length);
+ field_1_position_of_BOF = LittleEndian.getInt(buf);
field_2_option_flags = in.readUShort();
int field_3_sheetname_length = in.readUByte();
field_4_isMultibyteUnicode = in.readByte();
Modified: poi/trunk/src/java/org/apache/poi/hssf/record/FilePassRecord.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/FilePassRecord.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/FilePassRecord.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/FilePassRecord.java Wed Sep 28 23:36:09 2016
@@ -17,9 +17,22 @@
package org.apache.poi.hssf.record;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.poifs.crypt.EncryptionMode;
+import org.apache.poi.poifs.crypt.binaryrc4.BinaryRC4EncryptionHeader;
+import org.apache.poi.poifs.crypt.binaryrc4.BinaryRC4EncryptionVerifier;
+import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptionHeader;
+import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptionVerifier;
+import org.apache.poi.poifs.crypt.xor.XOREncryptionHeader;
+import org.apache.poi.poifs.crypt.xor.XOREncryptionVerifier;
import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
import org.apache.poi.util.LittleEndianOutput;
+import org.apache.poi.util.LittleEndianOutputStream;
/**
* Title: File Pass Record (0x002F) <p>
@@ -31,228 +44,92 @@ public final class FilePassRecord extend
private static final int ENCRYPTION_XOR = 0;
private static final int ENCRYPTION_OTHER = 1;
- private int _encryptionType;
- private KeyData _keyData;
-
- private static interface KeyData extends Cloneable {
- void read(RecordInputStream in);
- void serialize(LittleEndianOutput out);
- int getDataSize();
- void appendToString(StringBuffer buffer);
- KeyData clone(); // NOSONAR
- }
+ private final int encryptionType;
+ private EncryptionInfo encryptionInfo;
- public static final class Rc4KeyData implements KeyData, Cloneable {
- private static final int ENCRYPTION_OTHER_RC4 = 1;
- private static final int ENCRYPTION_OTHER_CAPI_2 = 2;
- private static final int ENCRYPTION_OTHER_CAPI_3 = 3;
- private static final int ENCRYPTION_OTHER_CAPI_4 = 4;
-
- private byte[] _salt;
- private byte[] _encryptedVerifier;
- private byte[] _encryptedVerifierHash;
- private int _encryptionInfo;
- private int _minorVersionNo;
-
- public void read(RecordInputStream in) {
- _encryptionInfo = in.readUShort();
- switch (_encryptionInfo) {
- case ENCRYPTION_OTHER_RC4:
- // handled below
- break;
- case ENCRYPTION_OTHER_CAPI_2:
- case ENCRYPTION_OTHER_CAPI_3:
- case ENCRYPTION_OTHER_CAPI_4:
- throw new EncryptedDocumentException(
- "HSSF does not currently support CryptoAPI encryption");
- default:
- throw new RecordFormatException("Unknown encryption info " + _encryptionInfo);
- }
- _minorVersionNo = in.readUShort();
- if (_minorVersionNo!=1) {
- throw new RecordFormatException("Unexpected VersionInfo number for RC4Header " + _minorVersionNo);
- }
- _salt = FilePassRecord.read(in, 16);
- _encryptedVerifier = FilePassRecord.read(in, 16);
- _encryptedVerifierHash = FilePassRecord.read(in, 16);
- }
-
- public void serialize(LittleEndianOutput out) {
- out.writeShort(_encryptionInfo);
- out.writeShort(_minorVersionNo);
- out.write(_salt);
- out.write(_encryptedVerifier);
- out.write(_encryptedVerifierHash);
- }
-
- public int getDataSize() {
- return 54;
- }
-
- public byte[] getSalt() {
- return _salt.clone();
- }
-
- public void setSalt(byte[] salt) {
- this._salt = salt.clone();
- }
-
- public byte[] getEncryptedVerifier() {
- return _encryptedVerifier.clone();
- }
-
- public void setEncryptedVerifier(byte[] encryptedVerifier) {
- this._encryptedVerifier = encryptedVerifier.clone();
- }
-
- public byte[] getEncryptedVerifierHash() {
- return _encryptedVerifierHash.clone();
- }
-
- public void setEncryptedVerifierHash(byte[] encryptedVerifierHash) {
- this._encryptedVerifierHash = encryptedVerifierHash.clone();
- }
-
- public void appendToString(StringBuffer buffer) {
- buffer.append(" .rc4.info = ").append(HexDump.shortToHex(_encryptionInfo)).append("\n");
- buffer.append(" .rc4.ver = ").append(HexDump.shortToHex(_minorVersionNo)).append("\n");
- buffer.append(" .rc4.salt = ").append(HexDump.toHex(_salt)).append("\n");
- buffer.append(" .rc4.verifier = ").append(HexDump.toHex(_encryptedVerifier)).append("\n");
- buffer.append(" .rc4.verifierHash = ").append(HexDump.toHex(_encryptedVerifierHash)).append("\n");
- }
-
- @Override
- public Rc4KeyData clone() {
- Rc4KeyData other = new Rc4KeyData();
- other._salt = this._salt.clone();
- other._encryptedVerifier = this._encryptedVerifier.clone();
- other._encryptedVerifierHash = this._encryptedVerifierHash.clone();
- other._encryptionInfo = this._encryptionInfo;
- other._minorVersionNo = this._minorVersionNo;
- return other;
- }
- }
-
- public static final class XorKeyData implements KeyData, Cloneable {
- /**
- * key (2 bytes): An unsigned integer that specifies the obfuscation key.
- * See [MS-OFFCRYPTO], 2.3.6.2 section, the first step of initializing XOR
- * array where it describes the generation of 16-bit XorKey value.
- */
- private int _key;
-
- /**
- * verificationBytes (2 bytes): An unsigned integer that specifies
- * the password verification identifier.
- */
- private int _verifier;
-
- public void read(RecordInputStream in) {
- _key = in.readUShort();
- _verifier = in.readUShort();
- }
-
- public void serialize(LittleEndianOutput out) {
- out.writeShort(_key);
- out.writeShort(_verifier);
- }
-
- public int getDataSize() {
- // TODO: Check!
- return 6;
- }
-
- public int getKey() {
- return _key;
- }
-
- public int getVerifier() {
- return _verifier;
- }
-
- public void setKey(int key) {
- this._key = key;
- }
-
- public void setVerifier(int verifier) {
- this._verifier = verifier;
- }
-
- public void appendToString(StringBuffer buffer) {
- buffer.append(" .xor.key = ").append(HexDump.intToHex(_key)).append("\n");
- buffer.append(" .xor.verifier = ").append(HexDump.intToHex(_verifier)).append("\n");
- }
-
- @Override
- public XorKeyData clone() {
- XorKeyData other = new XorKeyData();
- other._key = this._key;
- other._verifier = this._verifier;
- return other;
+ private FilePassRecord(FilePassRecord other) {
+ encryptionType = other.encryptionType;
+ try {
+ encryptionInfo = other.encryptionInfo.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new EncryptedDocumentException(e);
}
}
-
- private FilePassRecord(FilePassRecord other) {
- _encryptionType = other._encryptionType;
- _keyData = other._keyData.clone();
+ public FilePassRecord(EncryptionMode encryptionMode) {
+ encryptionType = (encryptionMode == EncryptionMode.xor) ? ENCRYPTION_XOR : ENCRYPTION_OTHER;
+ encryptionInfo = new EncryptionInfo(encryptionMode);
}
public FilePassRecord(RecordInputStream in) {
- _encryptionType = in.readUShort();
-
- switch (_encryptionType) {
- case ENCRYPTION_XOR:
- _keyData = new XorKeyData();
- break;
- case ENCRYPTION_OTHER:
- _keyData = new Rc4KeyData();
- break;
- default:
- throw new RecordFormatException("Unknown encryption type " + _encryptionType);
- }
-
- _keyData.read(in);
+ encryptionType = in.readUShort();
+
+ EncryptionMode preferredMode;
+ switch (encryptionType) {
+ case ENCRYPTION_XOR:
+ preferredMode = EncryptionMode.xor;
+ break;
+ case ENCRYPTION_OTHER:
+ preferredMode = EncryptionMode.cryptoAPI;
+ break;
+ default:
+ throw new EncryptedDocumentException("invalid encryption type");
+ }
+
+ try {
+ encryptionInfo = new EncryptionInfo(in, preferredMode);
+ } catch (IOException e) {
+ throw new EncryptedDocumentException(e);
+ }
}
- private static byte[] read(RecordInputStream in, int size) {
- byte[] result = new byte[size];
- in.readFully(result);
- return result;
- }
+ @SuppressWarnings("resource")
+ @Override
+ public void serialize(LittleEndianOutput out) {
+ out.writeShort(encryptionType);
+
+ byte data[] = new byte[1024];
+ LittleEndianByteArrayOutputStream bos = new LittleEndianByteArrayOutputStream(data, 0);
+
+ switch (encryptionInfo.getEncryptionMode()) {
+ case xor:
+ ((XOREncryptionHeader)encryptionInfo.getHeader()).write(bos);
+ ((XOREncryptionVerifier)encryptionInfo.getVerifier()).write(bos);
+ break;
+ case binaryRC4:
+ out.writeShort(encryptionInfo.getVersionMajor());
+ out.writeShort(encryptionInfo.getVersionMinor());
+ ((BinaryRC4EncryptionHeader)encryptionInfo.getHeader()).write(bos);
+ ((BinaryRC4EncryptionVerifier)encryptionInfo.getVerifier()).write(bos);
+ break;
+ case cryptoAPI:
+ out.writeShort(encryptionInfo.getVersionMajor());
+ out.writeShort(encryptionInfo.getVersionMinor());
+ out.writeInt(encryptionInfo.getEncryptionFlags());
+ ((CryptoAPIEncryptionHeader)encryptionInfo.getHeader()).write(bos);
+ ((CryptoAPIEncryptionVerifier)encryptionInfo.getVerifier()).write(bos);
+ break;
+ default:
+ throw new RuntimeException("not supported");
+ }
- public void serialize(LittleEndianOutput out) {
- out.writeShort(_encryptionType);
- assert(_keyData != null);
- _keyData.serialize(out);
+ out.write(data, 0, bos.getWriteIndex());
}
- protected int getDataSize() {
- assert(_keyData != null);
- return _keyData.getDataSize();
+ @Override
+ protected int getDataSize() {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ LittleEndianOutputStream leos = new LittleEndianOutputStream(bos);
+ serialize(leos);
+ return bos.size();
}
- public Rc4KeyData getRc4KeyData() {
- return (_keyData instanceof Rc4KeyData)
- ? (Rc4KeyData) _keyData
- : null;
- }
-
- public XorKeyData getXorKeyData() {
- return (_keyData instanceof XorKeyData)
- ? (XorKeyData) _keyData
- : null;
- }
-
- private Rc4KeyData checkRc4() {
- Rc4KeyData rc4 = getRc4KeyData();
- if (rc4 == null) {
- throw new RecordFormatException("file pass record doesn't contain a rc4 key.");
- }
- return rc4;
+ public EncryptionInfo getEncryptionInfo() {
+ return encryptionInfo;
}
- public short getSid() {
+ @Override
+ public short getSid() {
return sid;
}
@@ -261,12 +138,18 @@ public final class FilePassRecord extend
return new FilePassRecord(this);
}
- public String toString() {
+ @Override
+ public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("[FILEPASS]\n");
- buffer.append(" .type = ").append(HexDump.shortToHex(_encryptionType)).append("\n");
- _keyData.appendToString(buffer);
+ buffer.append(" .type = ").append(HexDump.shortToHex(encryptionType)).append("\n");
+ String prefix = " ."+encryptionInfo.getEncryptionMode();
+ buffer.append(prefix+".info = ").append(HexDump.shortToHex(encryptionInfo.getVersionMajor())).append("\n");
+ buffer.append(prefix+".ver = ").append(HexDump.shortToHex(encryptionInfo.getVersionMinor())).append("\n");
+ buffer.append(prefix+".salt = ").append(HexDump.toHex(encryptionInfo.getVerifier().getSalt())).append("\n");
+ buffer.append(prefix+".verifier = ").append(HexDump.toHex(encryptionInfo.getVerifier().getEncryptedVerifier())).append("\n");
+ buffer.append(prefix+".verifierHash = ").append(HexDump.toHex(encryptionInfo.getVerifier().getEncryptedVerifierHash())).append("\n");
buffer.append("[/FILEPASS]\n");
return buffer.toString();
}
Modified: poi/trunk/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java Wed Sep 28 23:36:09 2016
@@ -17,18 +17,16 @@
package org.apache.poi.hssf.record;
import java.io.InputStream;
+import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.List;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.hssf.eventusermodel.HSSFEventFactory;
import org.apache.poi.hssf.eventusermodel.HSSFListener;
-import org.apache.poi.hssf.record.FilePassRecord.Rc4KeyData;
-import org.apache.poi.hssf.record.FilePassRecord.XorKeyData;
import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
-import org.apache.poi.hssf.record.crypto.Biff8RC4Key;
-import org.apache.poi.hssf.record.crypto.Biff8XORKey;
import org.apache.poi.poifs.crypt.Decryptor;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
/**
* A stream based way to get at complete records, with
@@ -80,20 +78,16 @@ public final class RecordFactoryInputStr
outputRecs.add(rec);
}
- // If it's a FILEPASS, track it specifically but
- // don't include it in the main stream
+ // If it's a FILEPASS, track it specifically
if (rec instanceof FilePassRecord) {
fpr = (FilePassRecord) rec;
- outputRecs.remove(outputRecs.size()-1);
- // TODO - add fpr not added to outputRecs
- rec = outputRecs.get(0);
- } else {
- // workbook not encrypted (typical case)
- if (rec instanceof EOFRecord) {
- // A workbook stream is never empty, so crash instead
- // of trying to keep track of nesting level
- throw new IllegalStateException("Nothing between BOF and EOF");
- }
+ }
+
+ // workbook not encrypted (typical case)
+ if (rec instanceof EOFRecord) {
+ // A workbook stream is never empty, so crash instead
+ // of trying to keep track of nesting level
+ throw new IllegalStateException("Nothing between BOF and EOF");
}
}
} else {
@@ -114,31 +108,18 @@ public final class RecordFactoryInputStr
userPassword = Decryptor.DEFAULT_PASSWORD;
}
- Biff8EncryptionKey key;
- if (fpr.getRc4KeyData() != null) {
- Rc4KeyData rc4 = fpr.getRc4KeyData();
- Biff8RC4Key rc4key = Biff8RC4Key.create(userPassword, rc4.getSalt());
- key = rc4key;
- if (!rc4key.validate(rc4.getEncryptedVerifier(), rc4.getEncryptedVerifierHash())) {
- throw new EncryptedDocumentException(
- (Decryptor.DEFAULT_PASSWORD.equals(userPassword) ? "Default" : "Supplied")
- + " password is invalid for salt/verifier/verifierHash");
- }
- } else if (fpr.getXorKeyData() != null) {
- XorKeyData xor = fpr.getXorKeyData();
- Biff8XORKey xorKey = Biff8XORKey.create(userPassword, xor.getKey());
- key = xorKey;
-
- if (!xorKey.validate(userPassword, xor.getVerifier())) {
+ EncryptionInfo info = fpr.getEncryptionInfo();
+ try {
+ if (!info.getDecryptor().verifyPassword(userPassword)) {
throw new EncryptedDocumentException(
- (Decryptor.DEFAULT_PASSWORD.equals(userPassword) ? "Default" : "Supplied")
- + " password is invalid for key/verifier");
- }
- } else {
- throw new EncryptedDocumentException("Crypto API not yet supported.");
- }
+ (Decryptor.DEFAULT_PASSWORD.equals(userPassword) ? "Default" : "Supplied")
+ + " password is invalid for salt/verifier/verifierHash");
+ }
+ } catch (GeneralSecurityException e) {
+ throw new EncryptedDocumentException(e);
+ }
- return new RecordInputStream(original, key, _initialRecordsSize);
+ return new RecordInputStream(original, info, _initialRecordsSize);
}
public boolean hasEncryption() {
Modified: poi/trunk/src/java/org/apache/poi/hssf/record/RecordInputStream.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/RecordInputStream.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/RecordInputStream.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/RecordInputStream.java Wed Sep 28 23:36:09 2016
@@ -18,13 +18,14 @@
package org.apache.poi.hssf.record;
import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
import org.apache.poi.hssf.dev.BiffViewer;
import org.apache.poi.hssf.record.crypto.Biff8DecryptingStream;
-import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
-import org.apache.poi.util.LittleEndian;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.util.Internal;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInput;
import org.apache.poi.util.LittleEndianInputStream;
@@ -32,8 +33,6 @@ import org.apache.poi.util.LittleEndianI
/**
* Title: Record Input Stream<P>
* Description: Wraps a stream and provides helper methods for the construction of records.<P>
- *
- * @author Jason Height (jheight @ apache dot org)
*/
public final class RecordInputStream implements LittleEndianInput {
/** Maximum size of a single record (minus the 4 byte header) without a continue*/
@@ -91,6 +90,10 @@ public final class RecordInputStream imp
* index within the data section of the current BIFF record
*/
private int _currentDataOffset;
+ /**
+ * index within the data section when mark() was called
+ */
+ private int _markedDataOffset;
private static final class SimpleHeaderInput implements BiffHeaderInput {
@@ -117,14 +120,14 @@ public final class RecordInputStream imp
this (in, null, 0);
}
- public RecordInputStream(InputStream in, Biff8EncryptionKey key, int initialOffset) throws RecordFormatException {
+ public RecordInputStream(InputStream in, EncryptionInfo key, int initialOffset) throws RecordFormatException {
if (key == null) {
_dataInput = getLEI(in);
_bhi = new SimpleHeaderInput(in);
} else {
Biff8DecryptingStream bds = new Biff8DecryptingStream(in, initialOffset, key);
+ _dataInput = bds;
_bhi = bds;
- _dataInput = bds;
}
_nextSid = readNextSid();
}
@@ -305,13 +308,22 @@ public final class RecordInputStream imp
}
return result;
}
+
+ public void readPlain(byte[] buf, int off, int len) {
+ readFully(buf, 0, buf.length, true);
+ }
+
@Override
public void readFully(byte[] buf) {
- readFully(buf, 0, buf.length);
+ readFully(buf, 0, buf.length, false);
}
- @Override
+ @Override
public void readFully(byte[] buf, int off, int len) {
+ readFully(buf, off, len, false);
+ }
+
+ protected void readFully(byte[] buf, int off, int len, boolean isPlain) {
int origLen = len;
if (buf == null) {
throw new NullPointerException();
@@ -331,7 +343,11 @@ public final class RecordInputStream imp
}
}
checkRecordPosition(nextChunk);
- _dataInput.readFully(buf, off, nextChunk);
+ if (isPlain) {
+ _dataInput.readPlain(buf, off, nextChunk);
+ } else {
+ _dataInput.readFully(buf, off, nextChunk);
+ }
_currentDataOffset+=nextChunk;
off += nextChunk;
len -= nextChunk;
@@ -491,4 +507,31 @@ public final class RecordInputStream imp
public int getNextSid() {
return _nextSid;
}
+
+ /**
+ * Mark the stream position - experimental function
+ *
+ * @param readlimit the read ahead limit
+ *
+ * @see InputStream#mark(int)
+ */
+ @Internal
+ public void mark(int readlimit) {
+ ((InputStream)_dataInput).mark(readlimit);
+ _markedDataOffset = _currentDataOffset;
+ }
+
+ /**
+ * Resets the stream position to the previously marked position.
+ * Experimental function - this only works, when nextRecord() wasn't called in the meantime.
+ *
+ * @throws IOException if marking is not supported
+ *
+ * @see InputStream#reset()
+ */
+ @Internal
+ public void reset() throws IOException {
+ ((InputStream)_dataInput).reset();
+ _currentDataOffset = _markedDataOffset;
+ }
}
Modified: poi/trunk/src/java/org/apache/poi/hssf/record/cont/ContinuableRecordInput.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/cont/ContinuableRecordInput.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/cont/ContinuableRecordInput.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/cont/ContinuableRecordInput.java Wed Sep 28 23:36:09 2016
@@ -54,28 +54,34 @@ public class ContinuableRecordInput impl
public ContinuableRecordInput(RecordInputStream in){
_in = in;
}
+ @Override
public int available(){
return _in.available();
}
+ @Override
public byte readByte(){
return _in.readByte();
}
+ @Override
public int readUByte(){
return _in.readUByte();
}
+ @Override
public short readShort(){
return _in.readShort();
}
+ @Override
public int readUShort(){
int ch1 = readUByte();
int ch2 = readUByte();
return (ch2 << 8) + (ch1 << 0);
}
+ @Override
public int readInt(){
int ch1 = _in.readUByte();
int ch2 = _in.readUByte();
@@ -84,6 +90,7 @@ public class ContinuableRecordInput impl
return (ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0);
}
+ @Override
public long readLong(){
int b0 = _in.readUByte();
int b1 = _in.readUByte();
@@ -103,14 +110,23 @@ public class ContinuableRecordInput impl
(b0 << 0));
}
+ @Override
public double readDouble(){
return _in.readDouble();
}
+
+ @Override
public void readFully(byte[] buf){
_in.readFully(buf);
}
+
+ @Override
public void readFully(byte[] buf, int off, int len){
_in.readFully(buf, off, len);
}
-
+
+ @Override
+ public void readPlain(byte[] buf, int off, int len) {
+ readFully(buf, off, len);
+ }
}
Modified: poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8DecryptingStream.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8DecryptingStream.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8DecryptingStream.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8DecryptingStream.java Wed Sep 28 23:36:09 2016
@@ -18,102 +18,194 @@
package org.apache.poi.hssf.record.crypto;
import java.io.InputStream;
+import java.io.PushbackInputStream;
-import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.hssf.record.BOFRecord;
import org.apache.poi.hssf.record.BiffHeaderInput;
+import org.apache.poi.hssf.record.FilePassRecord;
+import org.apache.poi.hssf.record.InterfaceHdrRecord;
+import org.apache.poi.hssf.record.RecordFormatException;
+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.util.LittleEndian;
+import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInput;
-import org.apache.poi.util.LittleEndianInputStream;
-/**
- *
- * @author Josh Micich
- */
public final class Biff8DecryptingStream implements BiffHeaderInput, LittleEndianInput {
- private final LittleEndianInput _le;
- private final Biff8Cipher _cipher;
+ public static final int RC4_REKEYING_INTERVAL = 1024;
- public Biff8DecryptingStream(InputStream in, int initialOffset, Biff8EncryptionKey key) {
- if (key instanceof Biff8RC4Key) {
- _cipher = new Biff8RC4(initialOffset, (Biff8RC4Key)key);
- } else if (key instanceof Biff8XORKey) {
- _cipher = new Biff8XOR(initialOffset, (Biff8XORKey)key);
- } else {
- throw new EncryptedDocumentException("Crypto API not supported yet.");
- }
-
- if (in instanceof LittleEndianInput) {
- // accessing directly is an optimisation
- _le = (LittleEndianInput) in;
- } else {
- // less optimal, but should work OK just the same. Often occurs in junit tests.
- _le = new LittleEndianInputStream(in);
- }
- }
-
- public int available() {
- return _le.available();
+ private final EncryptionInfo info;
+ private ChunkedCipherInputStream ccis;
+ private final byte buffer[] = new byte[LittleEndianConsts.LONG_SIZE];
+ private boolean shouldSkipEncryptionOnCurrentRecord = false;
+
+ public Biff8DecryptingStream(InputStream in, int initialOffset, EncryptionInfo info) throws RecordFormatException {
+ try {
+ byte initialBuf[] = new byte[initialOffset];
+ InputStream stream;
+ if (initialOffset == 0) {
+ stream = in;
+ } else {
+ stream = new PushbackInputStream(in, initialOffset);
+ ((PushbackInputStream)stream).unread(initialBuf);
+ }
+
+ this.info = info;
+ Decryptor dec = this.info.getDecryptor();
+ dec.setChunkSize(RC4_REKEYING_INTERVAL);
+ ccis = (ChunkedCipherInputStream)dec.getDataStream(stream, Integer.MAX_VALUE, 0);
+
+ if (initialOffset > 0) {
+ ccis.readFully(initialBuf);
+ }
+ } catch (Exception e) {
+ throw new RecordFormatException(e);
+ }
+ }
+
+ @Override
+ public int available() {
+ return ccis.available();
}
/**
* Reads an unsigned short value without decrypting
*/
- public int readRecordSID() {
- int sid = _le.readUShort();
- _cipher.skipTwoBytes();
- _cipher.startRecord(sid);
+ @Override
+ public int readRecordSID() {
+ readPlain(buffer, 0, LittleEndianConsts.SHORT_SIZE);
+ int sid = LittleEndian.getUShort(buffer, 0);
+ shouldSkipEncryptionOnCurrentRecord = isNeverEncryptedRecord(sid);
return sid;
}
/**
* Reads an unsigned short value without decrypting
*/
- public int readDataSize() {
- int dataSize = _le.readUShort();
- _cipher.skipTwoBytes();
- _cipher.setNextRecordSize(dataSize);
+ @Override
+ public int readDataSize() {
+ readPlain(buffer, 0, LittleEndianConsts.SHORT_SIZE);
+ int dataSize = LittleEndian.getUShort(buffer, 0);
+ ccis.setNextRecordSize(dataSize);
return dataSize;
}
- public double readDouble() {
- long valueLongBits = readLong();
+ @Override
+ public double readDouble() {
+ long valueLongBits = readLong();
double result = Double.longBitsToDouble(valueLongBits);
if (Double.isNaN(result)) {
- throw new RuntimeException("Did not expect to read NaN"); // (Because Excel typically doesn't write NaN
+ // (Because Excel typically doesn't write NaN
+ throw new RuntimeException("Did not expect to read NaN");
}
return result;
}
- public void readFully(byte[] buf) {
- readFully(buf, 0, buf.length);
- }
-
- public void readFully(byte[] buf, int off, int len) {
- _le.readFully(buf, off, len);
- _cipher.xor(buf, off, len);
- }
-
-
- public int readUByte() {
- return readByte() & 0xFF;
- }
- public byte readByte() {
- return (byte) _cipher.xorByte(_le.readUByte());
- }
-
-
- public int readUShort() {
- return readShort() & 0xFFFF;
- }
- public short readShort() {
- return (short) _cipher.xorShort(_le.readUShort());
+ @Override
+ public void readFully(byte[] buf) {
+ readFully(buf, 0, buf.length);
+ }
+
+ @Override
+ public void readFully(byte[] buf, int off, int len) {
+ if (shouldSkipEncryptionOnCurrentRecord) {
+ readPlain(buf, off, buf.length);
+ } else {
+ ccis.readFully(buf, off, len);
+ }
+ }
+
+ @Override
+ public int readUByte() {
+ return readByte() & 0xFF;
+ }
+
+ @Override
+ public byte readByte() {
+ if (shouldSkipEncryptionOnCurrentRecord) {
+ readPlain(buffer, 0, LittleEndianConsts.BYTE_SIZE);
+ return buffer[0];
+ } else {
+ return ccis.readByte();
+ }
+ }
+
+ @Override
+ public int readUShort() {
+ return readShort() & 0xFFFF;
+ }
+
+ @Override
+ public short readShort() {
+ if (shouldSkipEncryptionOnCurrentRecord) {
+ readPlain(buffer, 0, LittleEndianConsts.SHORT_SIZE);
+ return LittleEndian.getShort(buffer);
+ } else {
+ return ccis.readShort();
+ }
+ }
+
+ @Override
+ public int readInt() {
+ if (shouldSkipEncryptionOnCurrentRecord) {
+ readPlain(buffer, 0, LittleEndianConsts.INT_SIZE);
+ return LittleEndian.getInt(buffer);
+ } else {
+ return ccis.readInt();
+ }
+ }
+
+ @Override
+ public long readLong() {
+ if (shouldSkipEncryptionOnCurrentRecord) {
+ readPlain(buffer, 0, LittleEndianConsts.LONG_SIZE);
+ return LittleEndian.getLong(buffer);
+ } else {
+ return ccis.readLong();
+ }
}
- public int readInt() {
- return _cipher.xorInt(_le.readInt());
+ /**
+ * @return the absolute position in the stream
+ */
+ public long getPosition() {
+ return ccis.getPos();
}
+
+ /**
+ * TODO: Additionally, the lbPlyPos (position_of_BOF) field of the BoundSheet8 record MUST NOT be encrypted.
+ *
+ * @return <code>true</code> if record type specified by <tt>sid</tt> is never encrypted
+ */
+ public static boolean isNeverEncryptedRecord(int sid) {
+ switch (sid) {
+ case BOFRecord.sid:
+ // sheet BOFs for sure
+ // TODO - find out about chart BOFs
+
+ case InterfaceHdrRecord.sid:
+ // don't know why this record doesn't seem to get encrypted
+
+ case FilePassRecord.sid:
+ // this only really counts when writing because FILEPASS is read early
+
+ // UsrExcl(0x0194)
+ // FileLock
+ // RRDInfo(0x0196)
+ // RRDHead(0x0138)
+
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void readPlain(byte b[], int off, int len) {
+ ccis.readPlain(b, off, len);
+ }
- public long readLong() {
- return _cipher.xorLong(_le.readLong());
- }
}
Modified: poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.java Wed Sep 28 23:36:09 2016
@@ -16,34 +16,9 @@
==================================================================== */
package org.apache.poi.hssf.record.crypto;
-import javax.crypto.SecretKey;
-
-import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
-import org.apache.poi.poifs.crypt.Decryptor;
-
-public abstract class Biff8EncryptionKey {
- protected SecretKey _secretKey;
-
- /**
- * Create using the default password and a specified docId
- * @param salt 16 bytes
- */
- public static Biff8EncryptionKey create(byte[] salt) {
- return Biff8RC4Key.create(Decryptor.DEFAULT_PASSWORD, salt);
- }
-
- public static Biff8EncryptionKey create(String password, byte[] salt) {
- return Biff8RC4Key.create(password, salt);
- }
-
- /**
- * @return <code>true</code> if the keyDigest is compatible with the specified saltData and saltHash
- */
- public boolean validate(byte[] saltData, byte[] saltHash) {
- throw new EncryptedDocumentException("validate is not supported (in super-class).");
- }
+public final class Biff8EncryptionKey {
/**
* Stores the BIFF8 encryption/decryption password for the current thread. This has been done
* using a {@link ThreadLocal} in order to avoid further overloading the various public APIs
Modified: poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java Wed Sep 28 23:36:09 2016
@@ -61,8 +61,10 @@ import org.apache.poi.hssf.model.Interna
import org.apache.poi.hssf.model.RecordStream;
import org.apache.poi.hssf.record.AbstractEscherHolderRecord;
import org.apache.poi.hssf.record.BackupRecord;
+import org.apache.poi.hssf.record.BoundSheetRecord;
import org.apache.poi.hssf.record.DrawingGroupRecord;
import org.apache.poi.hssf.record.ExtendedFormatRecord;
+import org.apache.poi.hssf.record.FilePassRecord;
import org.apache.poi.hssf.record.FontRecord;
import org.apache.poi.hssf.record.LabelRecord;
import org.apache.poi.hssf.record.LabelSSTRecord;
@@ -74,8 +76,11 @@ import org.apache.poi.hssf.record.SSTRec
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.util.CellReference;
+import org.apache.poi.poifs.crypt.ChunkedCipherOutputStream;
import org.apache.poi.poifs.crypt.Decryptor;
+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.DocumentNode;
@@ -101,6 +106,8 @@ import org.apache.poi.util.Configurator;
import org.apache.poi.util.HexDump;
import org.apache.poi.util.Internal;
import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianByteArrayInputStream;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
@@ -119,7 +126,6 @@ public final class HSSFWorkbook extends
* The maximum number of cell styles in a .xls workbook.
* The 'official' limit is 4,000, but POI allows a slightly larger number.
* This extra delta takes into account built-in styles that are automatically
- * created for new workbooks
*
* See http://office.microsoft.com/en-us/excel-help/excel-specifications-and-limits-HP005199291.aspx
*/
@@ -1444,7 +1450,7 @@ public final class HSSFWorkbook extends
if (log.check( POILogger.DEBUG )) {
log.log(DEBUG, "HSSFWorkbook.getBytes()");
}
-
+
HSSFSheet[] sheets = getSheets();
int nSheets = sheets.length;
@@ -1486,9 +1492,70 @@ public final class HSSFWorkbook extends
}
pos += serializedSize;
}
+
+ encryptBytes(retval);
+
return retval;
}
+ @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) {
+ return;
+ }
+
+ LittleEndianByteArrayInputStream plain = new LittleEndianByteArrayInputStream(buf, 0);
+ LittleEndianByteArrayOutputStream leos = new LittleEndianByteArrayOutputStream(buf, 0);
+ Encryptor enc = fpr.getEncryptionInfo().getEncryptor();
+ enc.setChunkSize(Biff8DecryptingStream.RC4_REKEYING_INTERVAL);
+ byte tmp[] = new byte[1024];
+ try {
+ ChunkedCipherOutputStream os = enc.getDataStream(leos, initialOffset);
+ int totalBytes = 0;
+ while (totalBytes < buf.length) {
+ plain.read(tmp, 0, 4);
+ final int sid = LittleEndian.getUShort(tmp, 0);
+ final int len = LittleEndian.getUShort(tmp, 2);
+ boolean isPlain = Biff8DecryptingStream.isNeverEncryptedRecord(sid);
+ os.setNextRecordSize(len, isPlain);
+ os.writePlain(tmp, 0, 4);
+ if (sid == BoundSheetRecord.sid) {
+ // special case for the field_1_position_of_BOF (=lbPlyPos) field of
+ // the BoundSheet8 record which must be unencrypted
+ byte bsrBuf[] = new byte[len];
+ plain.readFully(bsrBuf);
+ os.writePlain(bsrBuf, 0, 4);
+ os.write(bsrBuf, 4, len-4);
+ } else {
+ int todo = len;
+ while (todo > 0) {
+ int nextLen = Math.min(todo, tmp.length);
+ plain.readFully(tmp, 0, nextLen);
+ if (isPlain) {
+ os.writePlain(tmp, 0, nextLen);
+ } else {
+ os.write(tmp, 0, nextLen);
+ }
+ todo -= nextLen;
+ }
+ }
+ totalBytes += 4 + len;
+ }
+ os.close();
+ } catch (Exception e) {
+ throw new EncryptedDocumentException(e);
+ }
+ }
+
/*package*/ InternalWorkbook getWorkbook() {
return workbook;
}
Modified: poi/trunk/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java (original)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java Wed Sep 28 23:36:09 2016
@@ -16,78 +16,113 @@
==================================================================== */
package org.apache.poi.poifs.crypt;
+import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
+import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.ShortBufferException;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.util.Internal;
-import org.apache.poi.util.LittleEndianInput;
import org.apache.poi.util.LittleEndianInputStream;
@Internal
public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
- private final int chunkSize;
- private final int chunkMask;
- private final int chunkBits;
-
- private int _lastIndex = 0;
- private long _pos = 0;
- private long _size;
- private byte[] _chunk;
- private Cipher _cipher;
-
- public ChunkedCipherInputStream(LittleEndianInput stream, long size, int chunkSize)
- throws GeneralSecurityException {
- super((InputStream)stream);
+ private final int _chunkSize;
+ private final int _chunkBits;
+
+ private final long _size;
+ private final byte[] _chunk, _plain;
+ private final Cipher _cipher;
+
+ private int _lastIndex;
+ private long _pos;
+ private boolean _chunkIsValid = false;
+
+ public ChunkedCipherInputStream(InputStream stream, long size, int chunkSize)
+ throws GeneralSecurityException {
+ this(stream, size, chunkSize, 0);
+ }
+
+ public ChunkedCipherInputStream(InputStream stream, long size, int chunkSize, int initialPos)
+ throws GeneralSecurityException {
+ super(stream);
_size = size;
- this.chunkSize = chunkSize;
- chunkMask = chunkSize-1;
- chunkBits = Integer.bitCount(chunkMask);
-
- _cipher = initCipherForBlock(null, 0);
+ _pos = initialPos;
+ this._chunkSize = chunkSize;
+ int cs = chunkSize == -1 ? 4096 : chunkSize;
+ _chunk = new byte[cs];
+ _plain = new byte[cs];
+ _chunkBits = Integer.bitCount(_chunk.length-1);
+ _lastIndex = (int)(_pos >> _chunkBits);
+ _cipher = initCipherForBlock(null, _lastIndex);
}
-
+
+ public final Cipher initCipherForBlock(int block) throws IOException, GeneralSecurityException {
+ if (_chunkSize != -1) {
+ throw new GeneralSecurityException("the cipher block can only be set for streaming encryption, e.g. CryptoAPI...");
+ }
+
+ _chunkIsValid = false;
+ return initCipherForBlock(_cipher, block);
+ }
+
protected abstract Cipher initCipherForBlock(Cipher existing, int block)
throws GeneralSecurityException;
+ @Override
public int read() throws IOException {
byte[] b = new byte[1];
- if (read(b) == 1)
+ if (read(b) == 1) {
return b[0];
+ }
return -1;
}
// do not implement! -> recursion
// public int read(byte[] b) throws IOException;
+ @Override
public int read(byte[] b, int off, int len) throws IOException {
+ return read(b, off, len, false);
+ }
+
+ private int read(byte[] b, int off, int len, boolean readPlain) throws IOException {
int total = 0;
-
- if (available() <= 0) return -1;
+ if (available() <= 0) {
+ return -1;
+ }
+
+ final int chunkMask = getChunkMask();
while (len > 0) {
- if (_chunk == null) {
+ if (!_chunkIsValid) {
try {
- _chunk = nextChunk();
+ nextChunk();
+ _chunkIsValid = true;
} catch (GeneralSecurityException e) {
throw new EncryptedDocumentException(e.getMessage(), e);
}
}
- int count = (int)(chunkSize - (_pos & chunkMask));
+ int count = (int)(_chunk.length - (_pos & chunkMask));
int avail = available();
if (avail == 0) {
return total;
}
count = Math.min(avail, Math.min(count, len));
- System.arraycopy(_chunk, (int)(_pos & chunkMask), b, off, count);
+
+ System.arraycopy(readPlain ? _plain : _chunk, (int)(_pos & chunkMask), b, off, count);
+
off += count;
len -= count;
_pos += count;
- if ((_pos & chunkMask) == 0)
- _chunk = null;
+ if ((_pos & chunkMask) == 0) {
+ _chunkIsValid = false;
+ }
total += count;
}
@@ -95,21 +130,31 @@ public abstract class ChunkedCipherInput
}
@Override
- public long skip(long n) throws IOException {
+ public long skip(final long n) throws IOException {
long start = _pos;
- long skip = Math.min(available(), n);
+ long skip = Math.min(remainingBytes(), n);
- if ((((_pos + skip) ^ start) & ~chunkMask) != 0)
- _chunk = null;
+ if ((((_pos + skip) ^ start) & ~getChunkMask()) != 0) {
+ _chunkIsValid = false;
+ }
_pos += skip;
return skip;
}
@Override
public int available() {
+ return remainingBytes();
+ }
+
+ /**
+ * Helper method for forbidden available call - we know the size beforehand, so it's ok ...
+ *
+ * @return the remaining byte until EOF
+ */
+ private int remainingBytes() {
return (int)(_size - _pos);
}
-
+
@Override
public boolean markSupported() {
return false;
@@ -119,23 +164,115 @@ public abstract class ChunkedCipherInput
public synchronized void mark(int readlimit) {
throw new UnsupportedOperationException();
}
-
+
@Override
public synchronized void reset() throws IOException {
throw new UnsupportedOperationException();
}
- private byte[] nextChunk() throws GeneralSecurityException, IOException {
- int index = (int)(_pos >> chunkBits);
- initCipherForBlock(_cipher, index);
-
- if (_lastIndex != index) {
- super.skip((index - _lastIndex) << chunkBits);
+ protected int getChunkMask() {
+ return _chunk.length-1;
+ }
+
+ private void nextChunk() throws GeneralSecurityException, IOException {
+ if (_chunkSize != -1) {
+ int index = (int)(_pos >> _chunkBits);
+ initCipherForBlock(_cipher, index);
+
+ if (_lastIndex != index) {
+ super.skip((index - _lastIndex) << _chunkBits);
+ }
+
+ _lastIndex = index + 1;
+ }
+
+ final int todo = (int)Math.min(_size, _chunk.length);
+ int readBytes = 0, totalBytes = 0;
+ do {
+ readBytes = super.read(_plain, totalBytes, todo-totalBytes);
+ totalBytes += Math.max(0, readBytes);
+ } while (readBytes != -1 && totalBytes < todo);
+
+ if (readBytes == -1 && _pos+totalBytes < _size && _size < Integer.MAX_VALUE) {
+ throw new EOFException("buffer underrun");
}
- byte[] block = new byte[Math.min(super.available(), chunkSize)];
- super.read(block, 0, block.length);
- _lastIndex = index + 1;
- return _cipher.doFinal(block);
+ System.arraycopy(_plain, 0, _chunk, 0, totalBytes);
+
+ invokeCipher(totalBytes, _chunkSize > -1);
+ }
+
+ /**
+ * Helper function for overriding the cipher invocation, i.e. XOR doesn't use a cipher
+ * and uses it's own implementation
+ *
+ * @return
+ * @throws BadPaddingException
+ * @throws IllegalBlockSizeException
+ * @throws ShortBufferException
+ */
+ protected int invokeCipher(int totalBytes, boolean doFinal) throws GeneralSecurityException {
+ if (doFinal) {
+ return _cipher.doFinal(_chunk, 0, totalBytes, _chunk);
+ } else {
+ return _cipher.update(_chunk, 0, totalBytes, _chunk);
+ }
+ }
+
+ /**
+ * Used when BIFF header fields (sid, size) are being read. The internal
+ * {@link Cipher} instance must step even when unencrypted bytes are read
+ *
+ */
+ @Override
+ public void readPlain(byte b[], int off, int len) {
+ if (len <= 0) {
+ return;
+ }
+
+ try {
+ int readBytes, total = 0;
+ do {
+ readBytes = read(b, off, len, true);
+ total += Math.max(0, readBytes);
+ } while (readBytes > -1 && total < len);
+
+ if (total < len) {
+ throw new EOFException("buffer underrun");
+ }
+ } catch (IOException e) {
+ // need to wrap checked exception, because of LittleEndianInput interface :(
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Some ciphers (actually just XOR) are based on the record size,
+ * which needs to be set before decryption
+ *
+ * @param recordSize the size of the next record
+ */
+ public void setNextRecordSize(int recordSize) {
+ }
+
+ /**
+ * @return the chunk bytes
+ */
+ protected byte[] getChunk() {
+ return _chunk;
+ }
+
+ /**
+ * @return the plain bytes
+ */
+ protected byte[] getPlain() {
+ return _plain;
+ }
+
+ /**
+ * @return the absolute position in the stream
+ */
+ public long getPos() {
+ return _pos;
}
}
Modified: poi/trunk/src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java (original)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java Wed Sep 28 23:36:09 2016
@@ -25,13 +25,18 @@ import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
+import java.util.BitSet;
+import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.ShortBufferException;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.POIFSWriterEvent;
import org.apache.poi.poifs.filesystem.POIFSWriterListener;
+import org.apache.poi.util.IOUtils;
import org.apache.poi.util.Internal;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianConsts;
@@ -41,137 +46,246 @@ import org.apache.poi.util.TempFile;
@Internal
public abstract class ChunkedCipherOutputStream extends FilterOutputStream {
- private static final POILogger logger = POILogFactory.getLogger(ChunkedCipherOutputStream.class);
-
- protected final int chunkSize;
- protected final int chunkMask;
- protected final int chunkBits;
-
+ private static final POILogger LOG = POILogFactory.getLogger(ChunkedCipherOutputStream.class);
+ private static final int STREAMING = -1;
+
+ private final int _chunkSize;
+ private final int _chunkBits;
+
private final byte[] _chunk;
- private final File fileOut;
- private final DirectoryNode dir;
+ private final BitSet _plainByteFlags;
+ private final File _fileOut;
+ private final DirectoryNode _dir;
private long _pos = 0;
+ private long _totalPos = 0;
+ private long _written = 0;
private Cipher _cipher;
-
+
public ChunkedCipherOutputStream(DirectoryNode dir, int chunkSize) throws IOException, GeneralSecurityException {
super(null);
- this.chunkSize = chunkSize;
- chunkMask = chunkSize-1;
- chunkBits = Integer.bitCount(chunkMask);
- _chunk = new byte[chunkSize];
-
- fileOut = TempFile.createTempFile("encrypted_package", "crypt");
- fileOut.deleteOnExit();
- this.out = new FileOutputStream(fileOut);
- this.dir = dir;
+ this._chunkSize = chunkSize;
+ int cs = chunkSize == STREAMING ? 4096 : chunkSize;
+ _chunk = new byte[cs];
+ _plainByteFlags = new BitSet(cs);
+ _chunkBits = Integer.bitCount(cs-1);
+ _fileOut = TempFile.createTempFile("encrypted_package", "crypt");
+ _fileOut.deleteOnExit();
+ this.out = new FileOutputStream(_fileOut);
+ this._dir = dir;
_cipher = initCipherForBlock(null, 0, false);
}
+ public ChunkedCipherOutputStream(OutputStream stream, int chunkSize) throws IOException, GeneralSecurityException {
+ super(stream);
+ this._chunkSize = chunkSize;
+ int cs = chunkSize == STREAMING ? 4096 : chunkSize;
+ _chunk = new byte[cs];
+ _plainByteFlags = new BitSet(cs);
+ _chunkBits = Integer.bitCount(cs-1);
+ _fileOut = null;
+ _dir = null;
+ _cipher = initCipherForBlock(null, 0, false);
+ }
+
+ public final Cipher initCipherForBlock(int block, boolean lastChunk) throws IOException, GeneralSecurityException {
+ return initCipherForBlock(_cipher, block, lastChunk);
+ }
+
protected abstract Cipher initCipherForBlock(Cipher existing, int block, boolean lastChunk)
- throws GeneralSecurityException;
-
- @SuppressWarnings("hiding")
+ throws IOException, GeneralSecurityException;
+
protected abstract void calculateChecksum(File fileOut, int oleStreamSize)
throws GeneralSecurityException, IOException;
-
- @SuppressWarnings("hiding")
+
protected abstract void createEncryptionInfoEntry(DirectoryNode dir, File tmpFile)
throws IOException, GeneralSecurityException;
+ @Override
public void write(int b) throws IOException {
write(new byte[]{(byte)b});
}
+ @Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
- public void write(byte[] b, int off, int len)
- throws IOException {
- if (len == 0) return;
-
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ write(b, off, len, false);
+ }
+
+ public void writePlain(byte[] b, int off, int len) throws IOException {
+ write(b, off, len, true);
+ }
+
+ protected void write(byte[] b, int off, int len, boolean writePlain) throws IOException {
+ if (len == 0) {
+ return;
+ }
+
if (len < 0 || b.length < off+len) {
throw new IOException("not enough bytes in your input buffer");
}
-
+
+ final int chunkMask = getChunkMask();
while (len > 0) {
int posInChunk = (int)(_pos & chunkMask);
- int nextLen = Math.min(chunkSize-posInChunk, len);
+ int nextLen = Math.min(_chunk.length-posInChunk, len);
System.arraycopy(b, off, _chunk, posInChunk, nextLen);
+ if (writePlain) {
+ _plainByteFlags.set(posInChunk, posInChunk+nextLen);
+ }
_pos += nextLen;
+ _totalPos += nextLen;
off += nextLen;
len -= nextLen;
if ((_pos & chunkMask) == 0) {
- try {
- writeChunk();
- } catch (GeneralSecurityException e) {
- throw new IOException(e);
- }
+ writeChunk(len > 0);
}
}
}
- protected void writeChunk() throws IOException, GeneralSecurityException {
- int posInChunk = (int)(_pos & chunkMask);
+ protected int getChunkMask() {
+ return _chunk.length-1;
+ }
+
+ protected void writeChunk(boolean continued) throws IOException {
+ if (_pos == 0 || _totalPos == _written) {
+ return;
+ }
+
+ int posInChunk = (int)(_pos & getChunkMask());
+
// normally posInChunk is 0, i.e. on the next chunk (-> index-1)
// but if called on close(), posInChunk is somewhere within the chunk data
- int index = (int)(_pos >> chunkBits);
+ int index = (int)(_pos >> _chunkBits);
boolean lastChunk;
if (posInChunk==0) {
index--;
- posInChunk = chunkSize;
+ posInChunk = _chunk.length;
lastChunk = false;
} else {
// pad the last chunk
lastChunk = true;
}
- _cipher = initCipherForBlock(_cipher, index, lastChunk);
+ int ciLen;
+ try {
+ boolean doFinal = true;
+ long oldPos = _pos;
+ // reset stream (not only) in case we were interrupted by plain stream parts
+ // this also needs to be set to prevent an endless loop
+ _pos = 0;
+ if (_chunkSize == STREAMING) {
+ if (continued) {
+ doFinal = false;
+ }
+ } else {
+ _cipher = initCipherForBlock(_cipher, index, lastChunk);
+ // restore pos - only streaming chunks will be reset
+ _pos = oldPos;
+ }
+ ciLen = invokeCipher(posInChunk, doFinal);
+ } catch (GeneralSecurityException e) {
+ throw new IOException("can't re-/initialize cipher", e);
+ }
- int ciLen = _cipher.doFinal(_chunk, 0, posInChunk, _chunk);
out.write(_chunk, 0, ciLen);
+ _plainByteFlags.clear();
+ _written += ciLen;
+ }
+
+ /**
+ * Helper function for overriding the cipher invocation, i.e. XOR doesn't use a cipher
+ * and uses it's own implementation
+ *
+ * @return
+ * @throws BadPaddingException
+ * @throws IllegalBlockSizeException
+ * @throws ShortBufferException
+ */
+ protected int invokeCipher(int posInChunk, boolean doFinal) throws GeneralSecurityException {
+ byte plain[] = (_plainByteFlags.isEmpty()) ? null : _chunk.clone();
+
+ int ciLen = (doFinal)
+ ? _cipher.doFinal(_chunk, 0, posInChunk, _chunk)
+ : _cipher.update(_chunk, 0, posInChunk, _chunk);
+
+ for (int i = _plainByteFlags.nextSetBit(0); i >= 0 && i < posInChunk; i = _plainByteFlags.nextSetBit(i+1)) {
+ _chunk[i] = plain[i];
+ }
+
+ return ciLen;
}
+ @Override
public void close() throws IOException {
try {
- writeChunk();
+ writeChunk(false);
super.close();
-
- int oleStreamSize = (int)(fileOut.length()+LittleEndianConsts.LONG_SIZE);
- calculateChecksum(fileOut, (int)_pos);
- dir.createDocument(DEFAULT_POIFS_ENTRY, oleStreamSize, new EncryptedPackageWriter());
- createEncryptionInfoEntry(dir, fileOut);
+
+ if (_fileOut != null) {
+ int oleStreamSize = (int)(_fileOut.length()+LittleEndianConsts.LONG_SIZE);
+ calculateChecksum(_fileOut, (int)_pos);
+ _dir.createDocument(DEFAULT_POIFS_ENTRY, oleStreamSize, new EncryptedPackageWriter());
+ createEncryptionInfoEntry(_dir, _fileOut);
+ }
} catch (GeneralSecurityException e) {
throw new IOException(e);
}
}
+
+ protected byte[] getChunk() {
+ return _chunk;
+ }
+
+ protected BitSet getPlainByteFlags() {
+ return _plainByteFlags;
+ }
+
+ protected long getPos() {
+ return _pos;
+ }
+
+ protected long getTotalPos() {
+ return _totalPos;
+ }
+ /**
+ * Some ciphers (actually just XOR) are based on the record size,
+ * which needs to be set before encryption
+ *
+ * @param recordSize the size of the next record
+ * @param isPlain {@code true} if the record is unencrypted
+ */
+ public void setNextRecordSize(int recordSize, boolean isPlain) {
+ }
+
private class EncryptedPackageWriter implements POIFSWriterListener {
+ @Override
public void processPOIFSWriterEvent(POIFSWriterEvent event) {
try {
OutputStream os = event.getStream();
- byte buf[] = new byte[chunkSize];
-
- // StreamSize (8 bytes): An unsigned integer that specifies the number of bytes used by data
- // encrypted within the EncryptedData field, not including the size of the StreamSize field.
- // Note that the actual size of the \EncryptedPackage stream (1) can be larger than this
+
+ // StreamSize (8 bytes): An unsigned integer that specifies the number of bytes used by data
+ // encrypted within the EncryptedData field, not including the size of the StreamSize field.
+ // Note that the actual size of the \EncryptedPackage stream (1) can be larger than this
// value, depending on the block size of the chosen encryption algorithm
+ byte buf[] = new byte[LittleEndianConsts.LONG_SIZE];
LittleEndian.putLong(buf, 0, _pos);
- os.write(buf, 0, LittleEndian.LONG_SIZE);
+ os.write(buf);
- FileInputStream fis = new FileInputStream(fileOut);
- int readBytes;
- while ((readBytes = fis.read(buf)) != -1) {
- os.write(buf, 0, readBytes);
- }
+ FileInputStream fis = new FileInputStream(_fileOut);
+ IOUtils.copy(fis, os);
fis.close();
os.close();
-
- if (!fileOut.delete()) {
- logger.log(POILogger.ERROR, "Can't delete temporary encryption file: "+fileOut);
+
+ if (!_fileOut.delete()) {
+ LOG.log(POILogger.ERROR, "Can't delete temporary encryption file: "+_fileOut);
}
} catch (IOException e) {
throw new EncryptedDocumentException(e);
Modified: poi/trunk/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java (original)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java Wed Sep 28 23:36:09 2016
@@ -498,7 +498,9 @@ public class CryptoFunctions {
* @return the byte array for xor obfuscation
*/
public static byte[] createXorArray1(String password) {
- if (password.length() > 15) password = password.substring(0, 15);
+ if (password.length() > 15) {
+ password = password.substring(0, 15);
+ }
byte passBytes[] = password.getBytes(Charset.forName("ASCII"));
// this code is based on the libre office implementation.
Modified: poi/trunk/src/java/org/apache/poi/poifs/crypt/Decryptor.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/Decryptor.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/Decryptor.java (original)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/Decryptor.java Wed Sep 28 23:36:09 2016
@@ -20,7 +20,9 @@ import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
+import javax.crypto.Cipher;
import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.poifs.filesystem.DirectoryNode;
@@ -28,16 +30,15 @@ import org.apache.poi.poifs.filesystem.N
import org.apache.poi.poifs.filesystem.OPOIFSFileSystem;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
-public abstract class Decryptor {
+public abstract class Decryptor implements Cloneable {
public static final String DEFAULT_PASSWORD="VelvetSweatshop";
public static final String DEFAULT_POIFS_ENTRY="EncryptedPackage";
- protected final EncryptionInfoBuilder builder;
+ protected EncryptionInfo encryptionInfo;
private SecretKey secretKey;
private byte[] verifier, integrityHmacKey, integrityHmacValue;
- protected Decryptor(EncryptionInfoBuilder builder) {
- this.builder = builder;
+ protected Decryptor() {
}
/**
@@ -54,6 +55,45 @@ public abstract class Decryptor {
public abstract InputStream getDataStream(DirectoryNode dir)
throws IOException, GeneralSecurityException;
+ /**
+ * Wraps a stream for decryption<p>
+ *
+ * As we are handling streams and don't know the total length beforehand,
+ * it's the callers duty to care for the length of the entries.
+ *
+ * @param stream the stream to be wrapped
+ * @param initialPos initial/current byte position within the stream
+ * @return decrypted stream
+ */
+ public InputStream getDataStream(InputStream stream, int size, int initialPos)
+ throws IOException, GeneralSecurityException {
+ throw new RuntimeException("this decryptor doesn't support reading from a stream");
+ }
+
+ /**
+ * Sets the chunk size of the data stream.
+ * Needs to be set before the data stream is requested.
+ * When not set, the implementation uses method specific default values
+ *
+ * @param chunkSize the chunk size, i.e. the block size with the same encryption key
+ */
+ public void setChunkSize(int chunkSize) {
+ throw new RuntimeException("this decryptor doesn't support changing the chunk size");
+ }
+
+ /**
+ * Initializes a cipher object for a given block index for encryption
+ *
+ * @param cipher may be null, otherwise the given instance is reset to the new block index
+ * @param block the block index, e.g. the persist/slide id (hslf)
+ * @return a new cipher object, if cipher was null, otherwise the reinitialized cipher
+ * @throws GeneralSecurityException
+ */
+ public Cipher initCipherForBlock(Cipher cipher, int block)
+ throws GeneralSecurityException {
+ throw new RuntimeException("this decryptor doesn't support initCipherForBlock");
+ }
+
public abstract boolean verifyPassword(String password)
throws GeneralSecurityException;
@@ -85,9 +125,11 @@ public abstract class Decryptor {
public InputStream getDataStream(NPOIFSFileSystem fs) throws IOException, GeneralSecurityException {
return getDataStream(fs.getRoot());
}
+
public InputStream getDataStream(OPOIFSFileSystem fs) throws IOException, GeneralSecurityException {
return getDataStream(fs.getRoot());
}
+
public InputStream getDataStream(POIFSFileSystem fs) throws IOException, GeneralSecurityException {
return getDataStream(fs.getRoot());
}
@@ -126,10 +168,29 @@ public abstract class Decryptor {
}
protected int getBlockSizeInBytes() {
- return builder.getHeader().getBlockSize();
+ return encryptionInfo.getHeader().getBlockSize();
}
protected int getKeySizeInBytes() {
- return builder.getHeader().getKeySize()/8;
+ return encryptionInfo.getHeader().getKeySize()/8;
+ }
+
+ public EncryptionInfo getEncryptionInfo() {
+ return encryptionInfo;
+ }
+
+ public void setEncryptionInfo(EncryptionInfo encryptionInfo) {
+ this.encryptionInfo = encryptionInfo;
+ }
+
+ @Override
+ public Decryptor clone() throws CloneNotSupportedException {
+ Decryptor other = (Decryptor)super.clone();
+ other.integrityHmacKey = integrityHmacKey.clone();
+ other.integrityHmacValue = integrityHmacValue.clone();
+ other.verifier = verifier.clone();
+ other.secretKey = new SecretKeySpec(secretKey.getEncoded(), secretKey.getAlgorithm());
+ // encryptionInfo is set from outside
+ return other;
}
}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@poi.apache.org
For additional commands, e-mail: commits-help@poi.apache.org