diff --git a/src/java/org/apache/poi/POIDocument.java b/src/java/org/apache/poi/POIDocument.java index 87c6a14f6..d742c5572 100644 --- a/src/java/org/apache/poi/POIDocument.java +++ b/src/java/org/apache/poi/POIDocument.java @@ -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 implements Closeable { /* 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 implements Closeable { 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 diff --git a/src/java/org/apache/poi/hssf/model/InternalWorkbook.java b/src/java/org/apache/poi/hssf/model/InternalWorkbook.java index 84c6073c9..3a5fc1ef5 100644 --- a/src/java/org/apache/poi/hssf/model/InternalWorkbook.java +++ b/src/java/org/apache/poi/hssf/model/InternalWorkbook.java @@ -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.EscherAggregate; import org.apache.poi.hssf.record.ExtSSTRecord; import org.apache.poi.hssf.record.ExtendedFormatRecord; import org.apache.poi.hssf.record.ExternSheetRecord; +import org.apache.poi.hssf.record.FilePassRecord; import org.apache.poi.hssf.record.FileSharingRecord; import org.apache.poi.hssf.record.FnGroupCountRecord; import org.apache.poi.hssf.record.FontRecord; @@ -82,8 +87,13 @@ import org.apache.poi.hssf.record.WindowProtectRecord; import org.apache.poi.hssf.record.WriteAccessRecord; import org.apache.poi.hssf.record.WriteProtectRecord; import org.apache.poi.hssf.record.common.UnicodeString; +import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey; import org.apache.poi.hssf.util.HSSFColor; 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; diff --git a/src/java/org/apache/poi/hssf/record/BoundSheetRecord.java b/src/java/org/apache/poi/hssf/record/BoundSheetRecord.java index 5aa756d32..61b92831c 100644 --- a/src/java/org/apache/poi/hssf/record/BoundSheetRecord.java +++ b/src/java/org/apache/poi/hssf/record/BoundSheetRecord.java @@ -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 extends StandardRecord { * @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(); diff --git a/src/java/org/apache/poi/hssf/record/FilePassRecord.java b/src/java/org/apache/poi/hssf/record/FilePassRecord.java index 7c8ae948f..99f37e535 100644 --- a/src/java/org/apache/poi/hssf/record/FilePassRecord.java +++ b/src/java/org/apache/poi/hssf/record/FilePassRecord.java @@ -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)
@@ -31,228 +44,92 @@ public final class FilePassRecord extends StandardRecord implements Cloneable { 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 - } - - 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 final int encryptionType; + private EncryptionInfo encryptionInfo; private FilePassRecord(FilePassRecord other) { - _encryptionType = other._encryptionType; - _keyData = other._keyData.clone(); + encryptionType = other.encryptionType; + try { + encryptionInfo = other.encryptionInfo.clone(); + } catch (CloneNotSupportedException e) { + throw new EncryptedDocumentException(e); + } + } + + 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); - } - - private static byte[] read(RecordInputStream in, int size) { - byte[] result = new byte[size]; - in.readFully(result); - return result; - } - - public void serialize(LittleEndianOutput out) { - out.writeShort(_encryptionType); - assert(_keyData != null); - _keyData.serialize(out); - } - - protected int getDataSize() { - assert(_keyData != null); - return _keyData.getDataSize(); - } - - 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."); + 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"); } - return rc4; + + try { + encryptionInfo = new EncryptionInfo(in, preferredMode); + } catch (IOException e) { + throw new EncryptedDocumentException(e); + } + } + + @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"); + } + + out.write(data, 0, bos.getWriteIndex()); + } + + @Override + protected int getDataSize() { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + LittleEndianOutputStream leos = new LittleEndianOutputStream(bos); + serialize(leos); + return bos.size(); + } + + public EncryptionInfo getEncryptionInfo() { + return encryptionInfo; } - public short getSid() { + @Override + public short getSid() { return sid; } @@ -261,12 +138,18 @@ public final class FilePassRecord extends StandardRecord implements Cloneable { 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(); } diff --git a/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java b/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java index c7480147b..2c5ba94c0 100644 --- a/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java +++ b/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java @@ -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 RecordFactoryInputStream { 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 RecordFactoryInputStream { 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() { diff --git a/src/java/org/apache/poi/hssf/record/RecordInputStream.java b/src/java/org/apache/poi/hssf/record/RecordInputStream.java index f9212c798..8fd042393 100644 --- a/src/java/org/apache/poi/hssf/record/RecordInputStream.java +++ b/src/java/org/apache/poi/hssf/record/RecordInputStream.java @@ -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.LittleEndianInputStream; /** * Title: Record Input Stream
* Description: Wraps a stream and provides helper methods for the construction of records.
- *
- * @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 implements LittleEndianInput {
* 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 implements LittleEndianInput {
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 implements LittleEndianInput {
}
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 implements LittleEndianInput {
}
}
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 implements LittleEndianInput {
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;
+ }
}
diff --git a/src/java/org/apache/poi/hssf/record/cont/ContinuableRecordInput.java b/src/java/org/apache/poi/hssf/record/cont/ContinuableRecordInput.java
index 739aeacb8..ecd8d4b59 100644
--- a/src/java/org/apache/poi/hssf/record/cont/ContinuableRecordInput.java
+++ b/src/java/org/apache/poi/hssf/record/cont/ContinuableRecordInput.java
@@ -54,28 +54,34 @@ public class ContinuableRecordInput implements LittleEndianInput {
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 implements LittleEndianInput {
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 implements LittleEndianInput {
(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);
+ }
}
diff --git a/src/java/org/apache/poi/hssf/record/crypto/Biff8DecryptingStream.java b/src/java/org/apache/poi/hssf/record/crypto/Biff8DecryptingStream.java
index 564174b60..cef3102ff 100644
--- a/src/java/org/apache/poi/hssf/record/crypto/Biff8DecryptingStream.java
+++ b/src/java/org/apache/poi/hssf/record/crypto/Biff8DecryptingStream.java
@@ -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.");
- }
+ private final EncryptionInfo info;
+ private ChunkedCipherInputStream ccis;
+ private final byte buffer[] = new byte[LittleEndianConsts.LONG_SIZE];
+ private boolean shouldSkipEncryptionOnCurrentRecord = false;
- 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 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);
+ }
}
- public int available() {
- return _le.available();
+ @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);
+ @Override
+ 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);
+ @Override
+ public void readFully(byte[] buf, int off, int len) {
+ if (shouldSkipEncryptionOnCurrentRecord) {
+ readPlain(buf, off, buf.length);
+ } else {
+ ccis.readFully(buf, off, len);
+ }
}
-
- public int readUByte() {
- return readByte() & 0xFF;
+ @Override
+ public int readUByte() {
+ return readByte() & 0xFF;
}
- public byte readByte() {
- return (byte) _cipher.xorByte(_le.readUByte());
+
+ @Override
+ public byte readByte() {
+ if (shouldSkipEncryptionOnCurrentRecord) {
+ readPlain(buffer, 0, LittleEndianConsts.BYTE_SIZE);
+ return buffer[0];
+ } else {
+ return ccis.readByte();
+ }
}
-
- public int readUShort() {
- return readShort() & 0xFFFF;
+ @Override
+ public int readUShort() {
+ return readShort() & 0xFFFF;
}
- public short readShort() {
- return (short) _cipher.xorShort(_le.readUShort());
+
+ @Override
+ public short readShort() {
+ if (shouldSkipEncryptionOnCurrentRecord) {
+ readPlain(buffer, 0, LittleEndianConsts.SHORT_SIZE);
+ return LittleEndian.getShort(buffer);
+ } else {
+ return ccis.readShort();
+ }
}
- public int readInt() {
- return _cipher.xorInt(_le.readInt());
+ @Override
+ public int readInt() {
+ if (shouldSkipEncryptionOnCurrentRecord) {
+ readPlain(buffer, 0, LittleEndianConsts.INT_SIZE);
+ return LittleEndian.getInt(buffer);
+ } else {
+ return ccis.readInt();
+ }
}
- public long readLong() {
- return _cipher.xorLong(_le.readLong());
+ @Override
+ public long readLong() {
+ if (shouldSkipEncryptionOnCurrentRecord) {
+ readPlain(buffer, 0, LittleEndianConsts.LONG_SIZE);
+ return LittleEndian.getLong(buffer);
+ } else {
+ return ccis.readLong();
+ }
}
+
+ /**
+ * @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
+ *
+ * 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
diff --git a/src/java/org/apache/poi/poifs/crypt/EncryptionHeader.java b/src/java/org/apache/poi/poifs/crypt/EncryptionHeader.java
index b6a0e2d0c..c52e87c62 100644
--- a/src/java/org/apache/poi/poifs/crypt/EncryptionHeader.java
+++ b/src/java/org/apache/poi/poifs/crypt/EncryptionHeader.java
@@ -16,12 +16,11 @@
==================================================================== */
package org.apache.poi.poifs.crypt;
-
/**
* Reads and processes OOXML Encryption Headers
* The constants are largely based on ZIP constants.
*/
-public abstract class EncryptionHeader {
+public abstract class EncryptionHeader implements Cloneable {
public static final int ALGORITHM_RC4 = CipherAlgorithm.rc4.ecmaId;
public static final int ALGORITHM_AES_128 = CipherAlgorithm.aes128.ecmaId;
public static final int ALGORITHM_AES_192 = CipherAlgorithm.aes192.ecmaId;
@@ -132,4 +131,11 @@ public abstract class EncryptionHeader {
protected void setCspName(String cspName) {
this.cspName = cspName;
}
+
+ @Override
+ public EncryptionHeader clone() throws CloneNotSupportedException {
+ EncryptionHeader other = (EncryptionHeader)super.clone();
+ other.keySalt = (keySalt == null) ? null : keySalt.clone();
+ return other;
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java b/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java
index 780409e19..20115f1b4 100644
--- a/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java
+++ b/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java
@@ -20,6 +20,7 @@ import static org.apache.poi.poifs.crypt.EncryptionMode.agile;
import static org.apache.poi.poifs.crypt.EncryptionMode.binaryRC4;
import static org.apache.poi.poifs.crypt.EncryptionMode.cryptoAPI;
import static org.apache.poi.poifs.crypt.EncryptionMode.standard;
+import static org.apache.poi.poifs.crypt.EncryptionMode.xor;
import java.io.IOException;
@@ -34,15 +35,16 @@ import org.apache.poi.util.LittleEndianInput;
/**
*/
-public class EncryptionInfo {
+public class EncryptionInfo implements Cloneable {
+ private final EncryptionMode encryptionMode;
private final int versionMajor;
private final int versionMinor;
private final int encryptionFlags;
- private final EncryptionHeader header;
- private final EncryptionVerifier verifier;
- private final Decryptor decryptor;
- private final Encryptor encryptor;
+ private EncryptionHeader header;
+ private EncryptionVerifier verifier;
+ private Decryptor decryptor;
+ private Encryptor encryptor;
/**
* A flag that specifies whether CryptoAPI RC4 or ECMA-376 encryption
@@ -75,50 +77,55 @@ public class EncryptionInfo {
public EncryptionInfo(POIFSFileSystem fs) throws IOException {
this(fs.getRoot());
}
+
/**
* Opens for decryption
*/
public EncryptionInfo(OPOIFSFileSystem fs) throws IOException {
this(fs.getRoot());
}
+
/**
* Opens for decryption
*/
public EncryptionInfo(NPOIFSFileSystem fs) throws IOException {
this(fs.getRoot());
}
+
/**
* Opens for decryption
*/
public EncryptionInfo(DirectoryNode dir) throws IOException {
- this(dir.createDocumentInputStream("EncryptionInfo"), false);
+ this(dir.createDocumentInputStream("EncryptionInfo"), null);
}
- public EncryptionInfo(LittleEndianInput dis, boolean isCryptoAPI) throws IOException {
- final EncryptionMode encryptionMode;
- versionMajor = dis.readShort();
- versionMinor = dis.readShort();
+ public EncryptionInfo(LittleEndianInput dis, EncryptionMode preferredEncryptionMode) throws IOException {
+ if (preferredEncryptionMode == xor) {
+ versionMajor = xor.versionMajor;
+ versionMinor = xor.versionMinor;
+ } else {
+ versionMajor = dis.readUShort();
+ versionMinor = dis.readUShort();
+ }
- if (!isCryptoAPI
- && versionMajor == binaryRC4.versionMajor
+ if ( versionMajor == xor.versionMajor
+ && versionMinor == xor.versionMinor) {
+ encryptionMode = xor;
+ encryptionFlags = -1;
+ } else if ( versionMajor == binaryRC4.versionMajor
&& versionMinor == binaryRC4.versionMinor) {
encryptionMode = binaryRC4;
encryptionFlags = -1;
- } else if (!isCryptoAPI
- && versionMajor == agile.versionMajor
+ } else if (
+ 2 <= versionMajor && versionMajor <= 4
+ && versionMinor == 2) {
+ encryptionMode = (preferredEncryptionMode == cryptoAPI) ? cryptoAPI : standard;
+ encryptionFlags = dis.readInt();
+ } else if (
+ versionMajor == agile.versionMajor
&& versionMinor == agile.versionMinor){
encryptionMode = agile;
encryptionFlags = dis.readInt();
- } else if (!isCryptoAPI
- && 2 <= versionMajor && versionMajor <= 4
- && versionMinor == standard.versionMinor) {
- encryptionMode = standard;
- encryptionFlags = dis.readInt();
- } else if (isCryptoAPI
- && 2 <= versionMajor && versionMajor <= 4
- && versionMinor == cryptoAPI.versionMinor) {
- encryptionMode = cryptoAPI;
- encryptionFlags = dis.readInt();
} else {
encryptionFlags = dis.readInt();
throw new EncryptedDocumentException(
@@ -138,10 +145,6 @@ public class EncryptionInfo {
}
eib.initialize(this, dis);
- header = eib.getHeader();
- verifier = eib.getVerifier();
- decryptor = eib.getDecryptor();
- encryptor = eib.getEncryptor();
}
/**
@@ -175,6 +178,7 @@ public class EncryptionInfo {
, int blockSize
, ChainingMode chainingMode
) {
+ this.encryptionMode = encryptionMode;
versionMajor = encryptionMode.versionMajor;
versionMinor = encryptionMode.versionMinor;
encryptionFlags = encryptionMode.encryptionFlags;
@@ -187,11 +191,6 @@ public class EncryptionInfo {
}
eib.initialize(this, cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
-
- header = eib.getHeader();
- verifier = eib.getVerifier();
- decryptor = eib.getDecryptor();
- encryptor = eib.getEncryptor();
}
protected static EncryptionInfoBuilder getBuilder(EncryptionMode encryptionMode)
@@ -229,4 +228,36 @@ public class EncryptionInfo {
public Encryptor getEncryptor() {
return encryptor;
}
-}
+
+ public void setHeader(EncryptionHeader header) {
+ this.header = header;
+ }
+
+ public void setVerifier(EncryptionVerifier verifier) {
+ this.verifier = verifier;
+ }
+
+ public void setDecryptor(Decryptor decryptor) {
+ this.decryptor = decryptor;
+ }
+
+ public void setEncryptor(Encryptor encryptor) {
+ this.encryptor = encryptor;
+ }
+
+ public EncryptionMode getEncryptionMode() {
+ return encryptionMode;
+ }
+
+ @Override
+ public EncryptionInfo clone() throws CloneNotSupportedException {
+ EncryptionInfo other = (EncryptionInfo)super.clone();
+ other.header = header.clone();
+ other.verifier = verifier.clone();
+ other.decryptor = decryptor.clone();
+ other.decryptor.setEncryptionInfo(other);
+ other.encryptor = encryptor.clone();
+ other.encryptor.setEncryptionInfo(other);
+ return other;
+ }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/poi/poifs/crypt/EncryptionInfoBuilder.java b/src/java/org/apache/poi/poifs/crypt/EncryptionInfoBuilder.java
index e36d44da9..24371dfdd 100644
--- a/src/java/org/apache/poi/poifs/crypt/EncryptionInfoBuilder.java
+++ b/src/java/org/apache/poi/poifs/crypt/EncryptionInfoBuilder.java
@@ -30,24 +30,4 @@ public interface EncryptionInfoBuilder {
* initialize the builder from scratch
*/
void initialize(EncryptionInfo ei, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode);
-
- /**
- * @return the header data
- */
- EncryptionHeader getHeader();
-
- /**
- * @return the verifier data
- */
- EncryptionVerifier getVerifier();
-
- /**
- * @return the decryptor
- */
- Decryptor getDecryptor();
-
- /**
- * @return the encryptor
- */
- Encryptor getEncryptor();
}
diff --git a/src/java/org/apache/poi/poifs/crypt/EncryptionMode.java b/src/java/org/apache/poi/poifs/crypt/EncryptionMode.java
index 86f4b8508..50064b5a6 100644
--- a/src/java/org/apache/poi/poifs/crypt/EncryptionMode.java
+++ b/src/java/org/apache/poi/poifs/crypt/EncryptionMode.java
@@ -33,7 +33,9 @@ public enum EncryptionMode {
/* @see 2.3.4.5 \EncryptionInfo Stream (Standard Encryption) */
standard("org.apache.poi.poifs.crypt.standard.StandardEncryptionInfoBuilder", 4, 2, 0x24),
/* @see 2.3.4.10 \EncryptionInfo Stream (Agile Encryption) */
- agile("org.apache.poi.poifs.crypt.agile.AgileEncryptionInfoBuilder", 4, 4, 0x40)
+ agile("org.apache.poi.poifs.crypt.agile.AgileEncryptionInfoBuilder", 4, 4, 0x40),
+ /* @see XOR Obfuscation */
+ xor("org.apache.poi.poifs.crypt.xor.XOREncryptionInfoBuilder", 0, 0, 0)
;
public final String builder;
diff --git a/src/java/org/apache/poi/poifs/crypt/EncryptionVerifier.java b/src/java/org/apache/poi/poifs/crypt/EncryptionVerifier.java
index 2688b50f4..6e8059261 100644
--- a/src/java/org/apache/poi/poifs/crypt/EncryptionVerifier.java
+++ b/src/java/org/apache/poi/poifs/crypt/EncryptionVerifier.java
@@ -16,11 +16,10 @@
==================================================================== */
package org.apache.poi.poifs.crypt;
-
/**
* Used when checking if a key is valid for a document
*/
-public abstract class EncryptionVerifier {
+public abstract class EncryptionVerifier implements Cloneable {
private byte[] salt;
private byte[] encryptedVerifier;
private byte[] encryptedVerifierHash;
@@ -105,5 +104,13 @@ public abstract class EncryptionVerifier {
this.hashAlgorithm = hashAlgorithm;
}
-
+ @Override
+ public EncryptionVerifier clone() throws CloneNotSupportedException {
+ EncryptionVerifier other = (EncryptionVerifier)super.clone();
+ other.salt = (salt == null) ? null : salt.clone();
+ other.encryptedVerifier = (encryptedVerifier == null) ? null : encryptedVerifier.clone();
+ other.encryptedVerifierHash = (encryptedVerifierHash == null) ? null : encryptedVerifierHash.clone();
+ other.encryptedKey = (encryptedKey == null) ? null : encryptedKey.clone();
+ return other;
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/Encryptor.java b/src/java/org/apache/poi/poifs/crypt/Encryptor.java
index d40f8ae5d..546c96628 100644
--- a/src/java/org/apache/poi/poifs/crypt/Encryptor.java
+++ b/src/java/org/apache/poi/poifs/crypt/Encryptor.java
@@ -21,14 +21,16 @@ import java.io.OutputStream;
import java.security.GeneralSecurityException;
import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
import org.apache.poi.poifs.filesystem.OPOIFSFileSystem;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
-public abstract class Encryptor {
+public abstract class Encryptor implements Cloneable {
protected static final String DEFAULT_POIFS_ENTRY = Decryptor.DEFAULT_POIFS_ENTRY;
+ private EncryptionInfo encryptionInfo;
private SecretKey secretKey;
/**
@@ -59,11 +61,43 @@ public abstract class Encryptor {
return getDataStream(fs.getRoot());
}
+ public ChunkedCipherOutputStream getDataStream(OutputStream stream, int initialOffset)
+ throws IOException, GeneralSecurityException {
+ throw new RuntimeException("this decryptor doesn't support writing directly to a stream");
+ }
+
public SecretKey getSecretKey() {
return secretKey;
}
- protected void setSecretKey(SecretKey secretKey) {
+ public void setSecretKey(SecretKey secretKey) {
this.secretKey = secretKey;
}
+
+ public EncryptionInfo getEncryptionInfo() {
+ return encryptionInfo;
+ }
+
+ public void setEncryptionInfo(EncryptionInfo encryptionInfo) {
+ this.encryptionInfo = encryptionInfo;
+ }
+
+ /**
+ * 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");
+ }
+
+ @Override
+ public Encryptor clone() throws CloneNotSupportedException {
+ Encryptor other = (Encryptor)super.clone();
+ other.secretKey = new SecretKeySpec(secretKey.getEncoded(), secretKey.getAlgorithm());
+ // encryptionInfo is set from outside
+ return other;
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Decryptor.java b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Decryptor.java
index 5fb39d921..b6d8eda00 100644
--- a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Decryptor.java
+++ b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Decryptor.java
@@ -34,31 +34,38 @@ import org.apache.poi.poifs.filesystem.DocumentInputStream;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.StringUtil;
-public class BinaryRC4Decryptor extends Decryptor {
+public class BinaryRC4Decryptor extends Decryptor implements Cloneable {
private long _length = -1L;
+ private int _chunkSize = 512;
private class BinaryRC4CipherInputStream extends ChunkedCipherInputStream {
+ @Override
protected Cipher initCipherForBlock(Cipher existing, int block)
throws GeneralSecurityException {
- return BinaryRC4Decryptor.initCipherForBlock(existing, block, builder, getSecretKey(), Cipher.DECRYPT_MODE);
+ return BinaryRC4Decryptor.this.initCipherForBlock(existing, block);
}
public BinaryRC4CipherInputStream(DocumentInputStream stream, long size)
throws GeneralSecurityException {
- super(stream, size, 512);
+ super(stream, size, _chunkSize);
}
+
+ public BinaryRC4CipherInputStream(InputStream stream)
+ throws GeneralSecurityException {
+ super(stream, Integer.MAX_VALUE, _chunkSize);
+ }
}
- protected BinaryRC4Decryptor(BinaryRC4EncryptionInfoBuilder builder) {
- super(builder);
+ protected BinaryRC4Decryptor() {
}
+ @Override
public boolean verifyPassword(String password) {
- EncryptionVerifier ver = builder.getVerifier();
+ EncryptionVerifier ver = getEncryptionInfo().getVerifier();
SecretKey skey = generateSecretKey(password, ver);
try {
- Cipher cipher = initCipherForBlock(null, 0, builder, skey, Cipher.DECRYPT_MODE);
+ Cipher cipher = initCipherForBlock(null, 0, getEncryptionInfo(), skey, Cipher.DECRYPT_MODE);
byte encryptedVerifier[] = ver.getEncryptedVerifier();
byte verifier[] = new byte[encryptedVerifier.length];
cipher.update(encryptedVerifier, 0, encryptedVerifier.length, verifier);
@@ -78,17 +85,23 @@ public class BinaryRC4Decryptor extends Decryptor {
return false;
}
- protected static Cipher initCipherForBlock(Cipher cipher, int block,
- EncryptionInfoBuilder builder, SecretKey skey, int encryptMode)
+ @Override
+ public Cipher initCipherForBlock(Cipher cipher, int block)
throws GeneralSecurityException {
- EncryptionVerifier ver = builder.getVerifier();
+ return initCipherForBlock(cipher, block, getEncryptionInfo(), getSecretKey(), Cipher.DECRYPT_MODE);
+ }
+
+ protected static Cipher initCipherForBlock(Cipher cipher, int block,
+ EncryptionInfo encryptionInfo, SecretKey skey, int encryptMode)
+ throws GeneralSecurityException {
+ EncryptionVerifier ver = encryptionInfo.getVerifier();
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
byte blockKey[] = new byte[4];
LittleEndian.putUInt(blockKey, 0, block);
byte encKey[] = CryptoFunctions.generateKey(skey.getEncoded(), hashAlgo, blockKey, 16);
SecretKey key = new SecretKeySpec(encKey, skey.getAlgorithm());
if (cipher == null) {
- EncryptionHeader em = builder.getHeader();
+ EncryptionHeader em = encryptionInfo.getHeader();
cipher = CryptoFunctions.getCipher(key, em.getCipherAlgorithm(), null, null, encryptMode);
} else {
cipher.init(encryptMode, key);
@@ -96,10 +109,10 @@ public class BinaryRC4Decryptor extends Decryptor {
return cipher;
}
- protected static SecretKey generateSecretKey(String password,
- EncryptionVerifier ver) {
- if (password.length() > 255)
+ protected static SecretKey generateSecretKey(String password, EncryptionVerifier ver) {
+ if (password.length() > 255) {
password = password.substring(0, 255);
+ }
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
MessageDigest hashAlg = CryptoFunctions.getMessageDigest(hashAlgo);
byte hash[] = hashAlg.digest(StringUtil.getToUnicodeLE(password));
@@ -116,15 +129,23 @@ public class BinaryRC4Decryptor extends Decryptor {
return skey;
}
+ @Override
@SuppressWarnings("resource")
- public InputStream getDataStream(DirectoryNode dir) throws IOException,
+ public ChunkedCipherInputStream getDataStream(DirectoryNode dir) throws IOException,
GeneralSecurityException {
DocumentInputStream dis = dir.createDocumentInputStream(DEFAULT_POIFS_ENTRY);
_length = dis.readLong();
- BinaryRC4CipherInputStream cipherStream = new BinaryRC4CipherInputStream(dis, _length);
- return cipherStream;
+ return new BinaryRC4CipherInputStream(dis, _length);
}
+
+ @Override
+ public InputStream getDataStream(InputStream stream, int size, int initialPos)
+ throws IOException, GeneralSecurityException {
+ return new BinaryRC4CipherInputStream(stream);
+ }
+
+ @Override
public long getLength() {
if (_length == -1L) {
throw new IllegalStateException("Decryptor.getDataStream() was not called");
@@ -132,4 +153,14 @@ public class BinaryRC4Decryptor extends Decryptor {
return _length;
}
+
+ @Override
+ public void setChunkSize(int chunkSize) {
+ _chunkSize = chunkSize;
+ }
+
+ @Override
+ public BinaryRC4Decryptor clone() throws CloneNotSupportedException {
+ return (BinaryRC4Decryptor)super.clone();
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionHeader.java b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionHeader.java
index 1b811a103..b9022017b 100644
--- a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionHeader.java
+++ b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionHeader.java
@@ -24,8 +24,7 @@ import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
-public class BinaryRC4EncryptionHeader extends EncryptionHeader implements
- EncryptionRecord {
+public class BinaryRC4EncryptionHeader extends EncryptionHeader implements EncryptionRecord, Cloneable {
protected BinaryRC4EncryptionHeader() {
setCipherAlgorithm(CipherAlgorithm.rc4);
@@ -39,6 +38,14 @@ public class BinaryRC4EncryptionHeader extends EncryptionHeader implements
setChainingMode(null);
}
+ @Override
public void write(LittleEndianByteArrayOutputStream littleendianbytearrayoutputstream) {
}
+
+ @Override
+ public BinaryRC4EncryptionHeader clone() throws CloneNotSupportedException {
+ return (BinaryRC4EncryptionHeader)super.clone();
+ }
+
+
}
diff --git a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionInfoBuilder.java b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionInfoBuilder.java
index 10bf58d83..94ddde72a 100644
--- a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionInfoBuilder.java
+++ b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionInfoBuilder.java
@@ -23,55 +23,37 @@ import org.apache.poi.util.LittleEndianInput;
public class BinaryRC4EncryptionInfoBuilder implements EncryptionInfoBuilder {
- EncryptionInfo info;
- BinaryRC4EncryptionHeader header;
- BinaryRC4EncryptionVerifier verifier;
- BinaryRC4Decryptor decryptor;
- BinaryRC4Encryptor encryptor;
-
public BinaryRC4EncryptionInfoBuilder() {
}
+ @Override
public void initialize(EncryptionInfo info, LittleEndianInput dis)
throws IOException {
- this.info = info;
int vMajor = info.getVersionMajor();
int vMinor = info.getVersionMinor();
assert (vMajor == 1 && vMinor == 1);
- header = new BinaryRC4EncryptionHeader();
- verifier = new BinaryRC4EncryptionVerifier(dis);
- decryptor = new BinaryRC4Decryptor(this);
- encryptor = new BinaryRC4Encryptor(this);
+ info.setHeader(new BinaryRC4EncryptionHeader());
+ info.setVerifier(new BinaryRC4EncryptionVerifier(dis));
+ Decryptor dec = new BinaryRC4Decryptor();
+ dec.setEncryptionInfo(info);
+ info.setDecryptor(dec);
+ Encryptor enc = new BinaryRC4Encryptor();
+ enc.setEncryptionInfo(info);
+ info.setEncryptor(enc);
}
+ @Override
public void initialize(EncryptionInfo info,
CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm,
int keyBits, int blockSize, ChainingMode chainingMode) {
- this.info = info;
- header = new BinaryRC4EncryptionHeader();
- verifier = new BinaryRC4EncryptionVerifier();
- decryptor = new BinaryRC4Decryptor(this);
- encryptor = new BinaryRC4Encryptor(this);
- }
-
- public BinaryRC4EncryptionHeader getHeader() {
- return header;
- }
-
- public BinaryRC4EncryptionVerifier getVerifier() {
- return verifier;
- }
-
- public BinaryRC4Decryptor getDecryptor() {
- return decryptor;
- }
-
- public BinaryRC4Encryptor getEncryptor() {
- return encryptor;
- }
-
- public EncryptionInfo getEncryptionInfo() {
- return info;
+ info.setHeader(new BinaryRC4EncryptionHeader());
+ info.setVerifier(new BinaryRC4EncryptionVerifier());
+ Decryptor dec = new BinaryRC4Decryptor();
+ dec.setEncryptionInfo(info);
+ info.setDecryptor(dec);
+ Encryptor enc = new BinaryRC4Encryptor();
+ enc.setEncryptionInfo(info);
+ info.setEncryptor(enc);
}
}
diff --git a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionVerifier.java b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionVerifier.java
index 86cf4ac18..654b64a3e 100644
--- a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionVerifier.java
+++ b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionVerifier.java
@@ -23,7 +23,7 @@ import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
import org.apache.poi.util.LittleEndianInput;
-public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements EncryptionRecord {
+public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements EncryptionRecord, Cloneable {
protected BinaryRC4EncryptionVerifier() {
setSpinCount(-1);
@@ -50,6 +50,7 @@ public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements E
setHashAlgorithm(HashAlgorithm.md5);
}
+ @Override
protected void setSalt(byte salt[]) {
if (salt == null || salt.length != 16) {
throw new EncryptedDocumentException("invalid verifier salt");
@@ -58,14 +59,17 @@ public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements E
super.setSalt(salt);
}
+ @Override
protected void setEncryptedVerifier(byte encryptedVerifier[]) {
super.setEncryptedVerifier(encryptedVerifier);
}
+ @Override
protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) {
super.setEncryptedVerifierHash(encryptedVerifierHash);
}
+ @Override
public void write(LittleEndianByteArrayOutputStream bos) {
byte salt[] = getSalt();
assert (salt.length == 16);
@@ -78,4 +82,8 @@ public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements E
bos.write(encryptedVerifierHash);
}
+ @Override
+ public BinaryRC4EncryptionVerifier clone() throws CloneNotSupportedException {
+ return (BinaryRC4EncryptionVerifier)super.clone();
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Encryptor.java b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Encryptor.java
index 2cf2d9334..9545cbab0 100644
--- a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Encryptor.java
+++ b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Encryptor.java
@@ -34,39 +34,19 @@ import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.poifs.crypt.DataSpaceMapUtils;
import org.apache.poi.poifs.crypt.EncryptionInfo;
import org.apache.poi.poifs.crypt.Encryptor;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
-public class BinaryRC4Encryptor extends Encryptor {
+public class BinaryRC4Encryptor extends Encryptor implements Cloneable {
- private final BinaryRC4EncryptionInfoBuilder builder;
+ private int _chunkSize = 512;
- protected class BinaryRC4CipherOutputStream extends ChunkedCipherOutputStream {
-
- protected Cipher initCipherForBlock(Cipher cipher, int block, boolean lastChunk)
- throws GeneralSecurityException {
- return BinaryRC4Decryptor.initCipherForBlock(cipher, block, builder, getSecretKey(), Cipher.ENCRYPT_MODE);
- }
-
- protected void calculateChecksum(File file, int i) {
- }
-
- protected void createEncryptionInfoEntry(DirectoryNode dir, File tmpFile)
- throws IOException, GeneralSecurityException {
- BinaryRC4Encryptor.this.createEncryptionInfoEntry(dir);
- }
-
- public BinaryRC4CipherOutputStream(DirectoryNode dir)
- throws IOException, GeneralSecurityException {
- super(dir, 512);
- }
- }
-
- protected BinaryRC4Encryptor(BinaryRC4EncryptionInfoBuilder builder) {
- this.builder = builder;
+ protected BinaryRC4Encryptor() {
}
+ @Override
public void confirmPassword(String password) {
Random r = new SecureRandom();
byte salt[] = new byte[16];
@@ -76,20 +56,20 @@ public class BinaryRC4Encryptor extends Encryptor {
confirmPassword(password, null, null, verifier, salt, null);
}
+ @Override
public void confirmPassword(String password, byte keySpec[],
byte keySalt[], byte verifier[], byte verifierSalt[],
byte integritySalt[]) {
- BinaryRC4EncryptionVerifier ver = builder.getVerifier();
+ BinaryRC4EncryptionVerifier ver = (BinaryRC4EncryptionVerifier)getEncryptionInfo().getVerifier();
ver.setSalt(verifierSalt);
SecretKey skey = BinaryRC4Decryptor.generateSecretKey(password, ver);
setSecretKey(skey);
try {
- Cipher cipher = BinaryRC4Decryptor.initCipherForBlock(null, 0, builder, skey, Cipher.ENCRYPT_MODE);
+ Cipher cipher = BinaryRC4Decryptor.initCipherForBlock(null, 0, getEncryptionInfo(), skey, Cipher.ENCRYPT_MODE);
byte encryptedVerifier[] = new byte[16];
cipher.update(verifier, 0, 16, encryptedVerifier);
ver.setEncryptedVerifier(encryptedVerifier);
- org.apache.poi.poifs.crypt.HashAlgorithm hashAlgo = ver
- .getHashAlgorithm();
+ HashAlgorithm hashAlgo = ver.getHashAlgorithm();
MessageDigest hashAlg = CryptoFunctions.getMessageDigest(hashAlgo);
byte calcVerifierHash[] = hashAlg.digest(verifier);
byte encryptedVerifierHash[] = cipher.doFinal(calcVerifierHash);
@@ -99,22 +79,30 @@ public class BinaryRC4Encryptor extends Encryptor {
}
}
+ @Override
public OutputStream getDataStream(DirectoryNode dir)
throws IOException, GeneralSecurityException {
OutputStream countStream = new BinaryRC4CipherOutputStream(dir);
return countStream;
}
+ @Override
+ public BinaryRC4CipherOutputStream getDataStream(OutputStream stream, int initialOffset)
+ throws IOException, GeneralSecurityException {
+ return new BinaryRC4CipherOutputStream(stream);
+ }
+
protected int getKeySizeInBytes() {
- return builder.getHeader().getKeySize() / 8;
+ return getEncryptionInfo().getHeader().getKeySize() / 8;
}
protected void createEncryptionInfoEntry(DirectoryNode dir) throws IOException {
DataSpaceMapUtils.addDefaultDataSpace(dir);
- final EncryptionInfo info = builder.getEncryptionInfo();
- final BinaryRC4EncryptionHeader header = builder.getHeader();
- final BinaryRC4EncryptionVerifier verifier = builder.getVerifier();
+ final EncryptionInfo info = getEncryptionInfo();
+ final BinaryRC4EncryptionHeader header = (BinaryRC4EncryptionHeader)info.getHeader();
+ final BinaryRC4EncryptionVerifier verifier = (BinaryRC4EncryptionVerifier)info.getVerifier();
EncryptionRecord er = new EncryptionRecord() {
+ @Override
public void write(LittleEndianByteArrayOutputStream bos) {
bos.writeShort(info.getVersionMajor());
bos.writeShort(info.getVersionMinor());
@@ -124,4 +112,49 @@ public class BinaryRC4Encryptor extends Encryptor {
};
DataSpaceMapUtils.createEncryptionEntry(dir, "EncryptionInfo", er);
}
+
+ @Override
+ public void setChunkSize(int chunkSize) {
+ _chunkSize = chunkSize;
+ }
+
+ @Override
+ public BinaryRC4Encryptor clone() throws CloneNotSupportedException {
+ return (BinaryRC4Encryptor)super.clone();
+ }
+
+ protected class BinaryRC4CipherOutputStream extends ChunkedCipherOutputStream {
+
+ public BinaryRC4CipherOutputStream(OutputStream stream)
+ throws IOException, GeneralSecurityException {
+ super(stream, BinaryRC4Encryptor.this._chunkSize);
+ }
+
+ public BinaryRC4CipherOutputStream(DirectoryNode dir)
+ throws IOException, GeneralSecurityException {
+ super(dir, BinaryRC4Encryptor.this._chunkSize);
+ }
+
+ @Override
+ protected Cipher initCipherForBlock(Cipher cipher, int block, boolean lastChunk)
+ throws GeneralSecurityException {
+ return BinaryRC4Decryptor.initCipherForBlock(cipher, block, getEncryptionInfo(), getSecretKey(), Cipher.ENCRYPT_MODE);
+ }
+
+ @Override
+ protected void calculateChecksum(File file, int i) {
+ }
+
+ @Override
+ protected void createEncryptionInfoEntry(DirectoryNode dir, File tmpFile)
+ throws IOException, GeneralSecurityException {
+ BinaryRC4Encryptor.this.createEncryptionInfoEntry(dir);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ writeChunk(false);
+ super.flush();
+ }
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDecryptor.java b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDecryptor.java
index 09f82e5a6..451708c6e 100644
--- a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDecryptor.java
+++ b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDecryptor.java
@@ -17,7 +17,6 @@
package org.apache.poi.poifs.crypt.cryptoapi;
-import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -27,20 +26,20 @@ import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
-import javax.crypto.ShortBufferException;
import javax.crypto.spec.SecretKeySpec;
import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.poifs.crypt.ChunkedCipherInputStream;
import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.poifs.crypt.Decryptor;
import org.apache.poi.poifs.crypt.EncryptionHeader;
-import org.apache.poi.poifs.crypt.EncryptionInfoBuilder;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
import org.apache.poi.poifs.crypt.EncryptionVerifier;
import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.DocumentInputStream;
import org.apache.poi.poifs.filesystem.DocumentNode;
-import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.BoundedInputStream;
@@ -49,57 +48,11 @@ import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianInputStream;
import org.apache.poi.util.StringUtil;
-public class CryptoAPIDecryptor extends Decryptor {
+public class CryptoAPIDecryptor extends Decryptor implements Cloneable {
private long _length;
+ private int _chunkSize = -1;
- private class SeekableByteArrayInputStream extends ByteArrayInputStream {
- Cipher cipher;
- byte oneByte[] = { 0 };
-
- public void seek(int newpos) {
- if (newpos > count) {
- throw new ArrayIndexOutOfBoundsException(newpos);
- }
-
- this.pos = newpos;
- mark = newpos;
- }
-
- public void setBlock(int block) throws GeneralSecurityException {
- cipher = initCipherForBlock(cipher, block);
- }
-
- public synchronized int read() {
- int ch = super.read();
- if (ch == -1) return -1;
- oneByte[0] = (byte) ch;
- try {
- cipher.update(oneByte, 0, 1, oneByte);
- } catch (ShortBufferException e) {
- throw new EncryptedDocumentException(e);
- }
- return oneByte[0];
- }
-
- public synchronized int read(byte b[], int off, int len) {
- int readLen = super.read(b, off, len);
- if (readLen ==-1) return -1;
- try {
- cipher.update(b, off, readLen, b, off);
- } catch (ShortBufferException e) {
- throw new EncryptedDocumentException(e);
- }
- return readLen;
- }
-
- public SeekableByteArrayInputStream(byte buf[])
- throws GeneralSecurityException {
- super(buf);
- cipher = initCipherForBlock(null, 0);
- }
- }
-
static class StreamDescriptorEntry {
static BitField flagStream = BitFieldFactory.getInstance(1);
@@ -111,16 +64,16 @@ public class CryptoAPIDecryptor extends Decryptor {
String streamName;
}
- protected CryptoAPIDecryptor(CryptoAPIEncryptionInfoBuilder builder) {
- super(builder);
+ protected CryptoAPIDecryptor() {
_length = -1L;
}
+ @Override
public boolean verifyPassword(String password) {
- EncryptionVerifier ver = builder.getVerifier();
+ EncryptionVerifier ver = getEncryptionInfo().getVerifier();
SecretKey skey = generateSecretKey(password, ver);
try {
- Cipher cipher = initCipherForBlock(null, 0, builder, skey, Cipher.DECRYPT_MODE);
+ Cipher cipher = initCipherForBlock(null, 0, getEncryptionInfo(), skey, Cipher.DECRYPT_MODE);
byte encryptedVerifier[] = ver.getEncryptedVerifier();
byte verifier[] = new byte[encryptedVerifier.length];
cipher.update(encryptedVerifier, 0, encryptedVerifier.length, verifier);
@@ -140,30 +93,25 @@ public class CryptoAPIDecryptor extends Decryptor {
return false;
}
- /**
- * Initializes a cipher object for a given block index for decryption
- *
- * @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
- */
+ @Override
public Cipher initCipherForBlock(Cipher cipher, int block)
throws GeneralSecurityException {
- return initCipherForBlock(cipher, block, builder, getSecretKey(), Cipher.DECRYPT_MODE);
+ EncryptionInfo ei = getEncryptionInfo();
+ SecretKey sk = getSecretKey();
+ return initCipherForBlock(cipher, block, ei, sk, Cipher.DECRYPT_MODE);
}
protected static Cipher initCipherForBlock(Cipher cipher, int block,
- EncryptionInfoBuilder builder, SecretKey skey, int encryptMode)
+ EncryptionInfo encryptionInfo, SecretKey skey, int encryptMode)
throws GeneralSecurityException {
- EncryptionVerifier ver = builder.getVerifier();
+ EncryptionVerifier ver = encryptionInfo.getVerifier();
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
byte blockKey[] = new byte[4];
LittleEndian.putUInt(blockKey, 0, block);
MessageDigest hashAlg = CryptoFunctions.getMessageDigest(hashAlgo);
hashAlg.update(skey.getEncoded());
byte encKey[] = hashAlg.digest(blockKey);
- EncryptionHeader header = builder.getHeader();
+ EncryptionHeader header = encryptionInfo.getHeader();
int keyBits = header.getKeySize();
encKey = CryptoFunctions.getBlock0(encKey, keyBits / 8);
if (keyBits == 40) {
@@ -190,6 +138,18 @@ public class CryptoAPIDecryptor extends Decryptor {
return skey;
}
+ @Override
+ public ChunkedCipherInputStream getDataStream(DirectoryNode dir)
+ throws IOException, GeneralSecurityException {
+ throw new IOException("not supported");
+ }
+
+ @Override
+ public ChunkedCipherInputStream getDataStream(InputStream stream, int size, int initialPos)
+ throws IOException, GeneralSecurityException {
+ return new CryptoAPICipherInputStream(stream, size, initialPos);
+ }
+
/**
* Decrypt the Document-/SummaryInformation and other optionally streams.
* Opposed to other crypto modes, cryptoapi is record based and can't be used
@@ -197,15 +157,17 @@ public class CryptoAPIDecryptor extends Decryptor {
*
* @see 2.3.5.4 RC4 CryptoAPI Encrypted Summary Stream
*/
- public InputStream getDataStream(DirectoryNode dir)
+ public POIFSFileSystem getSummaryEntries(DirectoryNode root, String encryptedStream)
throws IOException, GeneralSecurityException {
- NPOIFSFileSystem fsOut = new NPOIFSFileSystem();
- DocumentNode es = (DocumentNode) dir.getEntry("EncryptedSummary");
- DocumentInputStream dis = dir.createDocumentInputStream(es);
+ POIFSFileSystem fsOut = new POIFSFileSystem();
+ // HSLF: encryptedStream
+ // HSSF: encryption
+ DocumentNode es = (DocumentNode) root.getEntry(encryptedStream);
+ DocumentInputStream dis = root.createDocumentInputStream(es);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
IOUtils.copy(dis, bos);
dis.close();
- SeekableByteArrayInputStream sbis = new SeekableByteArrayInputStream(bos.toByteArray());
+ CryptoAPIDocumentInputStream sbis = new CryptoAPIDocumentInputStream(this, bos.toByteArray());
LittleEndianInputStream leis = new LittleEndianInputStream(sbis);
int streamDescriptorArrayOffset = (int) leis.readUInt();
/* int streamDescriptorArraySize = (int) */ leis.readUInt();
@@ -239,21 +201,40 @@ public class CryptoAPIDecryptor extends Decryptor {
leis.close();
sbis.close();
sbis = null;
- bos.reset();
- fsOut.writeFilesystem(bos);
- fsOut.close();
- _length = bos.size();
- ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
- return bis;
+ return fsOut;
}
/**
* @return the length of the stream returned by {@link #getDataStream(DirectoryNode)}
*/
+ @Override
public long getLength() {
if (_length == -1L) {
throw new IllegalStateException("Decryptor.getDataStream() was not called");
}
return _length;
}
+
+ public void setChunkSize(int chunkSize) {
+ _chunkSize = chunkSize;
+ }
+
+ @Override
+ public CryptoAPIDecryptor clone() throws CloneNotSupportedException {
+ return (CryptoAPIDecryptor)super.clone();
+ }
+
+ private class CryptoAPICipherInputStream extends ChunkedCipherInputStream {
+
+ @Override
+ protected Cipher initCipherForBlock(Cipher existing, int block)
+ throws GeneralSecurityException {
+ return CryptoAPIDecryptor.this.initCipherForBlock(existing, block);
+ }
+
+ public CryptoAPICipherInputStream(InputStream stream, long size, int initialPos)
+ throws GeneralSecurityException {
+ super(stream, size, _chunkSize, initialPos);
+ }
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentInputStream.java b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentInputStream.java
new file mode 100644
index 000000000..573664932
--- /dev/null
+++ b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentInputStream.java
@@ -0,0 +1,86 @@
+/* ====================================================================
+ 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.cryptoapi;
+
+import java.io.ByteArrayInputStream;
+import java.security.GeneralSecurityException;
+
+import javax.crypto.Cipher;
+import javax.crypto.ShortBufferException;
+
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.util.Internal;
+
+/**
+ * A seekable InputStream, which is used to decrypt/extract the document entries
+ * within the encrypted stream
+ */
+@Internal
+/* package */ class CryptoAPIDocumentInputStream extends ByteArrayInputStream {
+ private Cipher cipher;
+ private final CryptoAPIDecryptor decryptor;
+ private byte oneByte[] = { 0 };
+
+ public void seek(int newpos) {
+ if (newpos > count) {
+ throw new ArrayIndexOutOfBoundsException(newpos);
+ }
+
+ this.pos = newpos;
+ mark = newpos;
+ }
+
+ public void setBlock(int block) throws GeneralSecurityException {
+ cipher = decryptor.initCipherForBlock(cipher, block);
+ }
+
+ @Override
+ public synchronized int read() {
+ int ch = super.read();
+ if (ch == -1) {
+ return -1;
+ }
+ oneByte[0] = (byte) ch;
+ try {
+ cipher.update(oneByte, 0, 1, oneByte);
+ } catch (ShortBufferException e) {
+ throw new EncryptedDocumentException(e);
+ }
+ return oneByte[0];
+ }
+
+ @Override
+ public synchronized int read(byte b[], int off, int len) {
+ int readLen = super.read(b, off, len);
+ if (readLen ==-1) {
+ return -1;
+ }
+ try {
+ cipher.update(b, off, readLen, b, off);
+ } catch (ShortBufferException e) {
+ throw new EncryptedDocumentException(e);
+ }
+ return readLen;
+ }
+
+ public CryptoAPIDocumentInputStream(CryptoAPIDecryptor decryptor, byte buf[])
+ throws GeneralSecurityException {
+ super(buf);
+ this.decryptor = decryptor;
+ cipher = decryptor.initCipherForBlock(null, 0);
+ }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentOutputStream.java b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentOutputStream.java
new file mode 100644
index 000000000..6bf04871d
--- /dev/null
+++ b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentOutputStream.java
@@ -0,0 +1,74 @@
+/* ====================================================================
+ 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.cryptoapi;
+
+import java.io.ByteArrayOutputStream;
+import java.security.GeneralSecurityException;
+
+import javax.crypto.Cipher;
+
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.util.Internal;
+
+/**
+ * An OutputStream for the document entries within the encrypted stream
+ */
+@Internal
+/* package */ class CryptoAPIDocumentOutputStream extends ByteArrayOutputStream {
+ private Cipher cipher;
+ private CryptoAPIEncryptor encryptor;
+ private byte oneByte[] = { 0 };
+
+ public CryptoAPIDocumentOutputStream(CryptoAPIEncryptor encryptor) throws GeneralSecurityException {
+ this.encryptor = encryptor;
+ setBlock(0);
+ }
+
+ public byte[] getBuf() {
+ return buf;
+ }
+
+ public void setSize(int count) {
+ this.count = count;
+ }
+
+ public void setBlock(int block) throws GeneralSecurityException {
+ cipher = encryptor.initCipherForBlock(cipher, block);
+ }
+
+ @Override
+ public void write(int b) {
+ try {
+ oneByte[0] = (byte)b;
+ cipher.update(oneByte, 0, 1, oneByte, 0);
+ super.write(oneByte);
+ } catch (Exception e) {
+ throw new EncryptedDocumentException(e);
+ }
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) {
+ try {
+ cipher.update(b, off, len, b, off);
+ super.write(b, off, len);
+ } catch (Exception e) {
+ throw new EncryptedDocumentException(e);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionHeader.java b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionHeader.java
index 151b6588a..b54dc2f0d 100644
--- a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionHeader.java
+++ b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionHeader.java
@@ -27,7 +27,7 @@ import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.crypt.standard.StandardEncryptionHeader;
import org.apache.poi.util.LittleEndianInput;
-public class CryptoAPIEncryptionHeader extends StandardEncryptionHeader {
+public class CryptoAPIEncryptionHeader extends StandardEncryptionHeader implements Cloneable {
public CryptoAPIEncryptionHeader(LittleEndianInput is) throws IOException {
super(is);
@@ -39,6 +39,7 @@ public class CryptoAPIEncryptionHeader extends StandardEncryptionHeader {
super(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
}
+ @Override
public void setKeySize(int keyBits) {
// Microsoft Base Cryptographic Provider is limited up to 40 bits
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa375599(v=vs.85).aspx
@@ -59,4 +60,9 @@ public class CryptoAPIEncryptionHeader extends StandardEncryptionHeader {
setCspName(CipherProvider.rc4.cipherProviderName);
}
}
+
+ @Override
+ public CryptoAPIEncryptionHeader clone() throws CloneNotSupportedException {
+ return (CryptoAPIEncryptionHeader)super.clone();
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionInfoBuilder.java b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionInfoBuilder.java
index 36df52876..fef4dde16 100644
--- a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionInfoBuilder.java
+++ b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionInfoBuilder.java
@@ -23,63 +23,52 @@ import org.apache.poi.poifs.crypt.*;
import org.apache.poi.util.LittleEndianInput;
public class CryptoAPIEncryptionInfoBuilder implements EncryptionInfoBuilder {
- EncryptionInfo info;
- CryptoAPIEncryptionHeader header;
- CryptoAPIEncryptionVerifier verifier;
- CryptoAPIDecryptor decryptor;
- CryptoAPIEncryptor encryptor;
-
public CryptoAPIEncryptionInfoBuilder() {
}
/**
* initialize the builder from a stream
*/
+ @Override
public void initialize(EncryptionInfo info, LittleEndianInput dis)
throws IOException {
- this.info = info;
/* int hSize = */ dis.readInt();
- header = new CryptoAPIEncryptionHeader(dis);
- verifier = new CryptoAPIEncryptionVerifier(dis, header);
- decryptor = new CryptoAPIDecryptor(this);
- encryptor = new CryptoAPIEncryptor(this);
+ CryptoAPIEncryptionHeader header = new CryptoAPIEncryptionHeader(dis);
+ info.setHeader(header);
+ info.setVerifier(new CryptoAPIEncryptionVerifier(dis, header));
+ CryptoAPIDecryptor dec = new CryptoAPIDecryptor();
+ dec.setEncryptionInfo(info);
+ info.setDecryptor(dec);
+ CryptoAPIEncryptor enc = new CryptoAPIEncryptor();
+ enc.setEncryptionInfo(info);
+ info.setEncryptor(enc);
}
/**
* initialize the builder from scratch
*/
+ @Override
public void initialize(EncryptionInfo info,
CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm,
int keyBits, int blockSize, ChainingMode chainingMode) {
- this.info = info;
- if (cipherAlgorithm == null) cipherAlgorithm = CipherAlgorithm.rc4;
- if (hashAlgorithm == null) hashAlgorithm = HashAlgorithm.sha1;
- if (keyBits == -1) keyBits = 0x28;
+ if (cipherAlgorithm == null) {
+ cipherAlgorithm = CipherAlgorithm.rc4;
+ }
+ if (hashAlgorithm == null) {
+ hashAlgorithm = HashAlgorithm.sha1;
+ }
+ if (keyBits == -1) {
+ keyBits = 0x28;
+ }
assert(cipherAlgorithm == CipherAlgorithm.rc4 && hashAlgorithm == HashAlgorithm.sha1);
- header = new CryptoAPIEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
- verifier = new CryptoAPIEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
- decryptor = new CryptoAPIDecryptor(this);
- encryptor = new CryptoAPIEncryptor(this);
- }
-
- public CryptoAPIEncryptionHeader getHeader() {
- return header;
- }
-
- public CryptoAPIEncryptionVerifier getVerifier() {
- return verifier;
- }
-
- public CryptoAPIDecryptor getDecryptor() {
- return decryptor;
- }
-
- public CryptoAPIEncryptor getEncryptor() {
- return encryptor;
- }
-
- public EncryptionInfo getEncryptionInfo() {
- return info;
+ info.setHeader(new CryptoAPIEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
+ info.setVerifier(new CryptoAPIEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
+ CryptoAPIDecryptor dec = new CryptoAPIDecryptor();
+ dec.setEncryptionInfo(info);
+ info.setDecryptor(dec);
+ CryptoAPIEncryptor enc = new CryptoAPIEncryptor();
+ enc.setEncryptionInfo(info);
+ info.setEncryptor(enc);
}
}
diff --git a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionVerifier.java b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionVerifier.java
index 160d1f9f9..d2c87b7ab 100644
--- a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionVerifier.java
+++ b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionVerifier.java
@@ -23,7 +23,7 @@ import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.crypt.standard.StandardEncryptionVerifier;
import org.apache.poi.util.LittleEndianInput;
-public class CryptoAPIEncryptionVerifier extends StandardEncryptionVerifier {
+public class CryptoAPIEncryptionVerifier extends StandardEncryptionVerifier implements Cloneable {
protected CryptoAPIEncryptionVerifier(LittleEndianInput is,
CryptoAPIEncryptionHeader header) {
@@ -36,15 +36,23 @@ public class CryptoAPIEncryptionVerifier extends StandardEncryptionVerifier {
super(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
}
+ @Override
protected void setSalt(byte salt[]) {
super.setSalt(salt);
}
+ @Override
protected void setEncryptedVerifier(byte encryptedVerifier[]) {
super.setEncryptedVerifier(encryptedVerifier);
}
+ @Override
protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) {
super.setEncryptedVerifierHash(encryptedVerifierHash);
}
+
+ @Override
+ public CryptoAPIEncryptionVerifier clone() throws CloneNotSupportedException {
+ return (CryptoAPIEncryptionVerifier)super.clone();
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptor.java b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptor.java
index 47d4696cb..e15558405 100644
--- a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptor.java
+++ b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptor.java
@@ -18,7 +18,7 @@
package org.apache.poi.poifs.crypt.cryptoapi;
import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
+import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
@@ -36,6 +36,7 @@ import org.apache.poi.hpsf.DocumentSummaryInformation;
import org.apache.poi.hpsf.PropertySetFactory;
import org.apache.poi.hpsf.SummaryInformation;
import org.apache.poi.hpsf.WritingNotSupportedException;
+import org.apache.poi.poifs.crypt.ChunkedCipherOutputStream;
import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.poifs.crypt.DataSpaceMapUtils;
import org.apache.poi.poifs.crypt.EncryptionInfo;
@@ -50,13 +51,14 @@ import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
import org.apache.poi.util.StringUtil;
-public class CryptoAPIEncryptor extends Encryptor {
- private final CryptoAPIEncryptionInfoBuilder builder;
-
- protected CryptoAPIEncryptor(CryptoAPIEncryptionInfoBuilder builder) {
- this.builder = builder;
+public class CryptoAPIEncryptor extends Encryptor implements Cloneable {
+
+ private int _chunkSize = 512;
+
+ protected CryptoAPIEncryptor() {
}
+ @Override
public void confirmPassword(String password) {
Random r = new SecureRandom();
byte salt[] = new byte[16];
@@ -66,11 +68,12 @@ public class CryptoAPIEncryptor extends Encryptor {
confirmPassword(password, null, null, verifier, salt, null);
}
+ @Override
public void confirmPassword(String password, byte keySpec[],
byte keySalt[], byte verifier[], byte verifierSalt[],
byte integritySalt[]) {
assert(verifier != null && verifierSalt != null);
- CryptoAPIEncryptionVerifier ver = builder.getVerifier();
+ CryptoAPIEncryptionVerifier ver = (CryptoAPIEncryptionVerifier)getEncryptionInfo().getVerifier();
ver.setSalt(verifierSalt);
SecretKey skey = CryptoAPIDecryptor.generateSecretKey(password, ver);
setSecretKey(skey);
@@ -99,8 +102,20 @@ public class CryptoAPIEncryptor extends Encryptor {
*/
public Cipher initCipherForBlock(Cipher cipher, int block)
throws GeneralSecurityException {
- return CryptoAPIDecryptor.initCipherForBlock(cipher, block, builder, getSecretKey(), Cipher.ENCRYPT_MODE);
+ return CryptoAPIDecryptor.initCipherForBlock(cipher, block, getEncryptionInfo(), getSecretKey(), Cipher.ENCRYPT_MODE);
}
+
+ @Override
+ public ChunkedCipherOutputStream getDataStream(DirectoryNode dir)
+ throws IOException, GeneralSecurityException {
+ throw new IOException("not supported");
+ }
+
+ @Override
+ public CryptoAPICipherOutputStream getDataStream(OutputStream stream, int initialOffset)
+ throws IOException, GeneralSecurityException {
+ return new CryptoAPICipherOutputStream(stream);
+ }
/**
* Encrypt the Document-/SummaryInformation and other optionally streams.
@@ -109,9 +124,9 @@ public class CryptoAPIEncryptor extends Encryptor {
*
* @see 2.3.5.4 RC4 CryptoAPI Encrypted Summary Stream
*/
- public OutputStream getDataStream(DirectoryNode dir)
+ public OutputStream getSummaryEntries(DirectoryNode dir)
throws IOException, GeneralSecurityException {
- CipherByteArrayOutputStream bos = new CipherByteArrayOutputStream();
+ CryptoAPIDocumentOutputStream bos = new CryptoAPIDocumentOutputStream(this);
byte buf[] = new byte[8];
bos.write(buf, 0, 8); // skip header
@@ -124,7 +139,9 @@ public class CryptoAPIEncryptor extends Encryptor {
int block = 0;
for (String entryName : entryNames) {
- if (!dir.hasEntry(entryName)) continue;
+ if (!dir.hasEntry(entryName)) {
+ continue;
+ }
StreamDescriptorEntry descEntry = new StreamDescriptorEntry();
descEntry.block = block;
descEntry.streamOffset = bos.size();
@@ -193,15 +210,21 @@ public class CryptoAPIEncryptor extends Encryptor {
}
protected int getKeySizeInBytes() {
- return builder.getHeader().getKeySize() / 8;
+ return getEncryptionInfo().getHeader().getKeySize() / 8;
}
+ @Override
+ public void setChunkSize(int chunkSize) {
+ _chunkSize = chunkSize;
+ }
+
protected void createEncryptionInfoEntry(DirectoryNode dir) throws IOException {
DataSpaceMapUtils.addDefaultDataSpace(dir);
- final EncryptionInfo info = builder.getEncryptionInfo();
- final CryptoAPIEncryptionHeader header = builder.getHeader();
- final CryptoAPIEncryptionVerifier verifier = builder.getVerifier();
+ final EncryptionInfo info = getEncryptionInfo();
+ final CryptoAPIEncryptionHeader header = (CryptoAPIEncryptionHeader)getEncryptionInfo().getHeader();
+ final CryptoAPIEncryptionVerifier verifier = (CryptoAPIEncryptionVerifier)getEncryptionInfo().getVerifier();
EncryptionRecord er = new EncryptionRecord() {
+ @Override
public void write(LittleEndianByteArrayOutputStream bos) {
bos.writeShort(info.getVersionMajor());
bos.writeShort(info.getVersionMinor());
@@ -212,44 +235,43 @@ public class CryptoAPIEncryptor extends Encryptor {
DataSpaceMapUtils.createEncryptionEntry(dir, "EncryptionInfo", er);
}
- private class CipherByteArrayOutputStream extends ByteArrayOutputStream {
- Cipher cipher;
- byte oneByte[] = { 0 };
-
- public CipherByteArrayOutputStream() throws GeneralSecurityException {
- setBlock(0);
- }
-
- public byte[] getBuf() {
- return buf;
- }
-
- public void setSize(int count) {
- this.count = count;
- }
-
- public void setBlock(int block) throws GeneralSecurityException {
- cipher = initCipherForBlock(cipher, block);
- }
-
- public void write(int b) {
- try {
- oneByte[0] = (byte)b;
- cipher.update(oneByte, 0, 1, oneByte, 0);
- super.write(oneByte);
- } catch (Exception e) {
- throw new EncryptedDocumentException(e);
- }
- }
-
- public void write(byte[] b, int off, int len) {
- try {
- cipher.update(b, off, len, b, off);
- super.write(b, off, len);
- } catch (Exception e) {
- throw new EncryptedDocumentException(e);
- }
- }
+ @Override
+ public CryptoAPIEncryptor clone() throws CloneNotSupportedException {
+ return (CryptoAPIEncryptor)super.clone();
}
+
+ protected class CryptoAPICipherOutputStream extends ChunkedCipherOutputStream {
+
+ @Override
+ protected Cipher initCipherForBlock(Cipher cipher, int block, boolean lastChunk)
+ throws IOException, GeneralSecurityException {
+ flush();
+ EncryptionInfo ei = getEncryptionInfo();
+ SecretKey sk = getSecretKey();
+ return CryptoAPIDecryptor.initCipherForBlock(cipher, block, ei, sk, Cipher.ENCRYPT_MODE);
+ }
+
+ @Override
+ protected void calculateChecksum(File file, int i) {
+ }
+
+ @Override
+ protected void createEncryptionInfoEntry(DirectoryNode dir, File tmpFile)
+ throws IOException, GeneralSecurityException {
+ throw new RuntimeException("createEncryptionInfoEntry not supported");
+ }
+
+ public CryptoAPICipherOutputStream(OutputStream stream)
+ throws IOException, GeneralSecurityException {
+ super(stream, CryptoAPIEncryptor.this._chunkSize);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ writeChunk(false);
+ super.flush();
+ }
+ }
+
}
diff --git a/src/java/org/apache/poi/poifs/crypt/standard/StandardDecryptor.java b/src/java/org/apache/poi/poifs/crypt/standard/StandardDecryptor.java
index a6d6dbfa0..d06f9a373 100644
--- a/src/java/org/apache/poi/poifs/crypt/standard/StandardDecryptor.java
+++ b/src/java/org/apache/poi/poifs/crypt/standard/StandardDecryptor.java
@@ -34,7 +34,6 @@ import org.apache.poi.poifs.crypt.ChainingMode;
import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.poifs.crypt.Decryptor;
import org.apache.poi.poifs.crypt.EncryptionHeader;
-import org.apache.poi.poifs.crypt.EncryptionInfoBuilder;
import org.apache.poi.poifs.crypt.EncryptionVerifier;
import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.filesystem.DirectoryNode;
@@ -44,15 +43,15 @@ import org.apache.poi.util.LittleEndian;
/**
*/
-public class StandardDecryptor extends Decryptor {
+public class StandardDecryptor extends Decryptor implements Cloneable {
private long _length = -1;
- protected StandardDecryptor(EncryptionInfoBuilder builder) {
- super(builder);
+ protected StandardDecryptor() {
}
+ @Override
public boolean verifyPassword(String password) {
- EncryptionVerifier ver = builder.getVerifier();
+ EncryptionVerifier ver = getEncryptionInfo().getVerifier();
SecretKey skey = generateSecretKey(password, ver, getKeySizeInBytes());
Cipher cipher = getCipher(skey);
@@ -116,12 +115,13 @@ public class StandardDecryptor extends Decryptor {
}
private Cipher getCipher(SecretKey key) {
- EncryptionHeader em = builder.getHeader();
+ EncryptionHeader em = getEncryptionInfo().getHeader();
ChainingMode cm = em.getChainingMode();
assert(cm == ChainingMode.ecb);
return CryptoFunctions.getCipher(key, em.getCipherAlgorithm(), cm, null, Cipher.DECRYPT_MODE);
}
+ @Override
@SuppressWarnings("resource")
public InputStream getDataStream(DirectoryNode dir) throws IOException {
DocumentInputStream dis = dir.createDocumentInputStream(DEFAULT_POIFS_ENTRY);
@@ -134,7 +134,7 @@ public class StandardDecryptor extends Decryptor {
// limit wrong calculated ole entries - (bug #57080)
// standard encryption always uses aes encoding, so blockSize is always 16
// http://stackoverflow.com/questions/3283787/size-of-data-after-aes-encryption
- int blockSize = builder.getHeader().getCipherAlgorithm().blockSize;
+ int blockSize = getEncryptionInfo().getHeader().getCipherAlgorithm().blockSize;
long cipherLen = (_length/blockSize + 1) * blockSize;
Cipher cipher = getCipher(getSecretKey());
@@ -145,8 +145,16 @@ public class StandardDecryptor extends Decryptor {
/**
* @return the length of the stream returned by {@link #getDataStream(DirectoryNode)}
*/
+ @Override
public long getLength(){
- if(_length == -1) throw new IllegalStateException("Decryptor.getDataStream() was not called");
+ if(_length == -1) {
+ throw new IllegalStateException("Decryptor.getDataStream() was not called");
+ }
return _length;
}
+
+ @Override
+ public StandardDecryptor clone() throws CloneNotSupportedException {
+ return (StandardDecryptor)super.clone();
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionHeader.java b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionHeader.java
index 44d7bc595..30f35581f 100644
--- a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionHeader.java
+++ b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionHeader.java
@@ -22,6 +22,7 @@ import static org.apache.poi.poifs.crypt.EncryptionInfo.flagCryptoAPI;
import java.io.IOException;
import java.io.InputStream;
+import org.apache.poi.hssf.record.RecordInputStream;
import org.apache.poi.poifs.crypt.ChainingMode;
import org.apache.poi.poifs.crypt.CipherAlgorithm;
import org.apache.poi.poifs.crypt.CipherProvider;
@@ -33,7 +34,7 @@ import org.apache.poi.util.LittleEndianInput;
import org.apache.poi.util.LittleEndianOutput;
import org.apache.poi.util.StringUtil;
-public class StandardEncryptionHeader extends EncryptionHeader implements EncryptionRecord {
+public class StandardEncryptionHeader extends EncryptionHeader implements EncryptionRecord, Cloneable {
protected StandardEncryptionHeader(LittleEndianInput is) throws IOException {
setFlags(is.readInt());
@@ -55,9 +56,17 @@ public class StandardEncryptionHeader extends EncryptionHeader implements Encryp
// CSPName may not always be specified
// In some cases, the salt value of the EncryptionVerifier is the next chunk of data
- ((InputStream)is).mark(LittleEndianConsts.INT_SIZE+1);
+ if (is instanceof RecordInputStream) {
+ ((RecordInputStream)is).mark(LittleEndianConsts.INT_SIZE+1);
+ } else {
+ ((InputStream)is).mark(LittleEndianConsts.INT_SIZE+1);
+ }
int checkForSalt = is.readInt();
- ((InputStream)is).reset();
+ if (is instanceof RecordInputStream) {
+ ((RecordInputStream)is).reset();
+ } else {
+ ((InputStream)is).reset();
+ }
if (checkForSalt == 16) {
setCspName("");
@@ -65,7 +74,9 @@ public class StandardEncryptionHeader extends EncryptionHeader implements Encryp
StringBuilder builder = new StringBuilder();
while (true) {
char c = (char) is.readShort();
- if (c == 0) break;
+ if (c == 0) {
+ break;
+ }
builder.append(c);
}
setCspName(builder.toString());
@@ -90,6 +101,7 @@ public class StandardEncryptionHeader extends EncryptionHeader implements Encryp
/**
* serializes the header
*/
+ @Override
public void write(LittleEndianByteArrayOutputStream bos) {
int startIdx = bos.getWriteIndex();
LittleEndianOutput sizeOutput = bos.createDelayedOutput(LittleEndianConsts.INT_SIZE);
@@ -102,10 +114,17 @@ public class StandardEncryptionHeader extends EncryptionHeader implements Encryp
bos.writeInt(0); // reserved1
bos.writeInt(0); // reserved2
String cspName = getCspName();
- if (cspName == null) cspName = getCipherProvider().cipherProviderName;
+ if (cspName == null) {
+ cspName = getCipherProvider().cipherProviderName;
+ }
bos.write(StringUtil.getToUnicodeLE(cspName));
bos.writeShort(0);
int headerSize = bos.getWriteIndex()-startIdx-LittleEndianConsts.INT_SIZE;
sizeOutput.writeInt(headerSize);
}
+
+ @Override
+ public StandardEncryptionHeader clone() throws CloneNotSupportedException {
+ return (StandardEncryptionHeader)super.clone();
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionInfoBuilder.java b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionInfoBuilder.java
index d55c8e0d6..c2bffdd05 100644
--- a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionInfoBuilder.java
+++ b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionInfoBuilder.java
@@ -27,34 +27,29 @@ import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.util.LittleEndianInput;
public class StandardEncryptionInfoBuilder implements EncryptionInfoBuilder {
-
- EncryptionInfo info;
- StandardEncryptionHeader header;
- StandardEncryptionVerifier verifier;
- StandardDecryptor decryptor;
- StandardEncryptor encryptor;
/**
* initialize the builder from a stream
*/
+ @Override
public void initialize(EncryptionInfo info, LittleEndianInput dis) throws IOException {
- this.info = info;
-
/* int hSize = */ dis.readInt();
- header = new StandardEncryptionHeader(dis);
- verifier = new StandardEncryptionVerifier(dis, header);
+ StandardEncryptionHeader header = new StandardEncryptionHeader(dis);
+ info.setHeader(header);
+ info.setVerifier(new StandardEncryptionVerifier(dis, header));
if (info.getVersionMinor() == 2 && (info.getVersionMajor() == 3 || info.getVersionMajor() == 4)) {
- decryptor = new StandardDecryptor(this);
+ StandardDecryptor dec = new StandardDecryptor();
+ dec.setEncryptionInfo(info);
+ info.setDecryptor(dec);
}
}
/**
* initialize the builder from scratch
*/
+ @Override
public void initialize(EncryptionInfo info, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
- this.info = info;
-
if (cipherAlgorithm == null) {
cipherAlgorithm = CipherAlgorithm.aes128;
}
@@ -89,29 +84,13 @@ public class StandardEncryptionInfoBuilder implements EncryptionInfoBuilder {
if (!found) {
throw new EncryptedDocumentException("KeySize "+keyBits+" not allowed for Cipher "+cipherAlgorithm.toString());
}
- header = new StandardEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
- verifier = new StandardEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
- decryptor = new StandardDecryptor(this);
- encryptor = new StandardEncryptor(this);
- }
-
- public StandardEncryptionHeader getHeader() {
- return header;
- }
-
- public StandardEncryptionVerifier getVerifier() {
- return verifier;
- }
-
- public StandardDecryptor getDecryptor() {
- return decryptor;
- }
-
- public StandardEncryptor getEncryptor() {
- return encryptor;
- }
-
- public EncryptionInfo getEncryptionInfo() {
- return info;
+ info.setHeader(new StandardEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
+ info.setVerifier(new StandardEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
+ StandardDecryptor dec = new StandardDecryptor();
+ dec.setEncryptionInfo(info);
+ info.setDecryptor(dec);
+ StandardEncryptor enc = new StandardEncryptor();
+ enc.setEncryptionInfo(info);
+ info.setEncryptor(enc);
}
}
diff --git a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionVerifier.java b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionVerifier.java
index 021d82f9e..f00efecfb 100644
--- a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionVerifier.java
+++ b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionVerifier.java
@@ -27,7 +27,7 @@ import org.apache.poi.util.LittleEndianInput;
/**
* Used when checking if a key is valid for a document
*/
-public class StandardEncryptionVerifier extends EncryptionVerifier implements EncryptionRecord {
+public class StandardEncryptionVerifier extends EncryptionVerifier implements EncryptionRecord, Cloneable {
private static final int SPIN_COUNT = 50000;
private final int verifierHashSize;
@@ -68,6 +68,7 @@ public class StandardEncryptionVerifier extends EncryptionVerifier implements En
}
// make method visible for this package
+ @Override
protected void setSalt(byte salt[]) {
if (salt == null || salt.length != 16) {
throw new EncryptedDocumentException("invalid verifier salt");
@@ -76,15 +77,18 @@ public class StandardEncryptionVerifier extends EncryptionVerifier implements En
}
// make method visible for this package
+ @Override
protected void setEncryptedVerifier(byte encryptedVerifier[]) {
super.setEncryptedVerifier(encryptedVerifier);
}
// make method visible for this package
+ @Override
protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) {
super.setEncryptedVerifierHash(encryptedVerifierHash);
}
+ @Override
public void write(LittleEndianByteArrayOutputStream bos) {
// see [MS-OFFCRYPTO] - 2.3.4.9
byte salt[] = getSalt();
@@ -115,4 +119,9 @@ public class StandardEncryptionVerifier extends EncryptionVerifier implements En
protected int getVerifierHashSize() {
return verifierHashSize;
}
+
+ @Override
+ public StandardEncryptionVerifier clone() throws CloneNotSupportedException {
+ return (StandardEncryptionVerifier)super.clone();
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptor.java b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptor.java
index fded6c606..5372c2af8 100644
--- a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptor.java
+++ b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptor.java
@@ -53,15 +53,13 @@ import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.TempFile;
-public class StandardEncryptor extends Encryptor {
+public class StandardEncryptor extends Encryptor implements Cloneable {
private static final POILogger logger = POILogFactory.getLogger(StandardEncryptor.class);
- private final StandardEncryptionInfoBuilder builder;
-
- protected StandardEncryptor(StandardEncryptionInfoBuilder builder) {
- this.builder = builder;
+ protected StandardEncryptor() {
}
+ @Override
public void confirmPassword(String password) {
// see [MS-OFFCRYPTO] - 2.3.3 EncryptionVerifier
Random r = new SecureRandom();
@@ -79,8 +77,9 @@ public class StandardEncryptor extends Encryptor {
*
* see [MS-OFFCRYPTO] - 2.3.4.7 ECMA-376 Document Encryption Key Generation
*/
+ @Override
public void confirmPassword(String password, byte keySpec[], byte keySalt[], byte verifier[], byte verifierSalt[], byte integritySalt[]) {
- StandardEncryptionVerifier ver = builder.getVerifier();
+ StandardEncryptionVerifier ver = (StandardEncryptionVerifier)getEncryptionInfo().getVerifier();
ver.setSalt(verifierSalt);
SecretKey secretKey = generateSecretKey(password, ver, getKeySizeInBytes());
@@ -111,10 +110,11 @@ public class StandardEncryptor extends Encryptor {
}
private Cipher getCipher(SecretKey key, String padding) {
- EncryptionVerifier ver = builder.getVerifier();
+ EncryptionVerifier ver = getEncryptionInfo().getVerifier();
return CryptoFunctions.getCipher(key, ver.getCipherAlgorithm(), ver.getChainingMode(), null, Cipher.ENCRYPT_MODE, padding);
}
+ @Override
public OutputStream getDataStream(final DirectoryNode dir)
throws IOException, GeneralSecurityException {
createEncryptionInfoEntry(dir);
@@ -163,6 +163,7 @@ public class StandardEncryptor extends Encryptor {
countBytes++;
}
+ @Override
public void close() throws IOException {
// the CipherOutputStream adds the padding bytes on close()
super.close();
@@ -175,6 +176,7 @@ public class StandardEncryptor extends Encryptor {
// TODO: any properties???
}
+ @Override
public void processPOIFSWriterEvent(POIFSWriterEvent event) {
try {
LittleEndianOutputStream leos = new LittleEndianOutputStream(event.getStream());
@@ -200,15 +202,16 @@ public class StandardEncryptor extends Encryptor {
}
protected int getKeySizeInBytes() {
- return builder.getHeader().getKeySize()/8;
+ return getEncryptionInfo().getHeader().getKeySize()/8;
}
protected void createEncryptionInfoEntry(DirectoryNode dir) throws IOException {
- final EncryptionInfo info = builder.getEncryptionInfo();
- final StandardEncryptionHeader header = builder.getHeader();
- final StandardEncryptionVerifier verifier = builder.getVerifier();
+ final EncryptionInfo info = getEncryptionInfo();
+ final StandardEncryptionHeader header = (StandardEncryptionHeader)info.getHeader();
+ final StandardEncryptionVerifier verifier = (StandardEncryptionVerifier)info.getVerifier();
EncryptionRecord er = new EncryptionRecord(){
+ @Override
public void write(LittleEndianByteArrayOutputStream bos) {
bos.writeShort(info.getVersionMajor());
bos.writeShort(info.getVersionMinor());
@@ -222,4 +225,9 @@ public class StandardEncryptor extends Encryptor {
// TODO: any properties???
}
+
+ @Override
+ public StandardEncryptor clone() throws CloneNotSupportedException {
+ return (StandardEncryptor)super.clone();
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/xor/XORDecryptor.java b/src/java/org/apache/poi/poifs/crypt/xor/XORDecryptor.java
new file mode 100644
index 000000000..01725f48b
--- /dev/null
+++ b/src/java/org/apache/poi/poifs/crypt/xor/XORDecryptor.java
@@ -0,0 +1,174 @@
+/* ====================================================================
+ 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.xor;
+
+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.poifs.crypt.ChunkedCipherInputStream;
+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.filesystem.DirectoryNode;
+import org.apache.poi.util.LittleEndian;
+
+public class XORDecryptor extends Decryptor implements Cloneable {
+ private long _length = -1L;
+ private int _chunkSize = 512;
+
+ protected XORDecryptor() {
+ }
+
+ @Override
+ public boolean verifyPassword(String password) {
+ XOREncryptionVerifier ver = (XOREncryptionVerifier)getEncryptionInfo().getVerifier();
+ int keyVer = LittleEndian.getUShort(ver.getEncryptedKey());
+ int verifierVer = LittleEndian.getUShort(ver.getEncryptedVerifier());
+ int keyComp = CryptoFunctions.createXorKey1(password);
+ int verifierComp = CryptoFunctions.createXorVerifier1(password);
+ if (keyVer == keyComp && verifierVer == verifierComp) {
+ byte xorArray[] = CryptoFunctions.createXorArray1(password);
+ setSecretKey(new SecretKeySpec(xorArray, "XOR"));
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public Cipher initCipherForBlock(Cipher cipher, int block)
+ throws GeneralSecurityException {
+ return null;
+ }
+
+ protected static Cipher initCipherForBlock(Cipher cipher, int block,
+ EncryptionInfo encryptionInfo, SecretKey skey, int encryptMode)
+ throws GeneralSecurityException {
+ return null;
+ }
+
+ @Override
+ public ChunkedCipherInputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException {
+ throw new RuntimeException("not supported");
+ }
+
+ @Override
+ public InputStream getDataStream(InputStream stream, int size, int initialPos)
+ throws IOException, GeneralSecurityException {
+ return new XORCipherInputStream(stream, initialPos);
+ }
+
+
+ @Override
+ public long getLength() {
+ if (_length == -1L) {
+ throw new IllegalStateException("Decryptor.getDataStream() was not called");
+ }
+
+ return _length;
+ }
+
+ @Override
+ public void setChunkSize(int chunkSize) {
+ _chunkSize = chunkSize;
+ }
+
+ @Override
+ public XORDecryptor clone() throws CloneNotSupportedException {
+ return (XORDecryptor)super.clone();
+ }
+
+ private class XORCipherInputStream extends ChunkedCipherInputStream {
+ private final int _initialOffset;
+ private int _recordStart = 0;
+ private int _recordEnd = 0;
+
+ public XORCipherInputStream(InputStream stream, int initialPos)
+ throws GeneralSecurityException {
+ super(stream, Integer.MAX_VALUE, _chunkSize);
+ _initialOffset = initialPos;
+ }
+
+ @Override
+ protected Cipher initCipherForBlock(Cipher existing, int block)
+ throws GeneralSecurityException {
+ return XORDecryptor.this.initCipherForBlock(existing, block);
+ }
+
+ @Override
+ protected int invokeCipher(int totalBytes, boolean doFinal) {
+ final int pos = (int)getPos();
+ final byte xorArray[] = getEncryptionInfo().getDecryptor().getSecretKey().getEncoded();
+ final byte chunk[] = getChunk();
+ final byte plain[] = getPlain();
+ final int posInChunk = pos & getChunkMask();
+
+ /*
+ * From: http://social.msdn.microsoft.com/Forums/en-US/3dadbed3-0e68-4f11-8b43-3a2328d9ebd5
+ *
+ * The initial value for XorArrayIndex is as follows:
+ * XorArrayIndex = (FileOffset + Data.Length) % 16
+ *
+ * The FileOffset variable in this context is the stream offset into the Workbook stream at
+ * the time we are about to write each of the bytes of the record data.
+ * This (the value) is then incremented after each byte is written.
+ */
+ final int xorArrayIndex = _initialOffset+_recordEnd+(pos-_recordStart);
+
+ for (int i=0; pos+i < _recordEnd && i < totalBytes; i++) {
+ // The following is taken from the Libre Office implementation
+ // It seems that the encrypt and decrypt method is mixed up
+ // in the MS-OFFCRYPTO docs
+ byte value = plain[posInChunk+i];
+ value = rotateLeft(value, 3);
+ value ^= xorArray[(xorArrayIndex+i) & 0x0F];
+ chunk[posInChunk+i] = value;
+ }
+
+ // the other bytes will be encoded, when setNextRecordSize is called the next time
+ return totalBytes;
+ }
+
+ private byte rotateLeft(byte bits, int shift) {
+ return (byte)(((bits & 0xff) << shift) | ((bits & 0xff) >>> (8 - shift)));
+ }
+
+
+ /**
+ * Decrypts a xor obfuscated byte array.
+ * The data is decrypted in-place
+ *
+ * @see 2.3.7.3 Binary Document XOR Data Transformation Method 1
+ */
+ @Override
+ public void setNextRecordSize(int recordSize) {
+ final int pos = (int)getPos();
+ final byte chunk[] = getChunk();
+ final int chunkMask = getChunkMask();
+ _recordStart = pos;
+ _recordEnd = _recordStart+recordSize;
+ int nextBytes = Math.min(recordSize, chunk.length-(pos & chunkMask));
+ invokeCipher(nextBytes, true);
+ }
+ }
+}
diff --git a/src/java/org/apache/poi/hssf/record/crypto/Biff8Cipher.java b/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionHeader.java
similarity index 61%
rename from src/java/org/apache/poi/hssf/record/crypto/Biff8Cipher.java
rename to src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionHeader.java
index 8ac742e0d..873c1abde 100644
--- a/src/java/org/apache/poi/hssf/record/crypto/Biff8Cipher.java
+++ b/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionHeader.java
@@ -1,30 +1,37 @@
-/* ====================================================================
- 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.hssf.record.crypto;
-
-
-public interface Biff8Cipher {
- void startRecord(int currentSid);
- void setNextRecordSize(int recordSize);
- void skipTwoBytes();
- void xor(byte[] buf, int pOffset, int pLen);
- int xorByte(int rawVal);
- int xorShort(int rawVal);
- int xorInt(int rawVal);
- long xorLong(long rawVal);
-}
+/* ====================================================================
+ 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.xor;
+
+import org.apache.poi.poifs.crypt.EncryptionHeader;
+import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+
+public class XOREncryptionHeader extends EncryptionHeader implements EncryptionRecord, Cloneable {
+
+ protected XOREncryptionHeader() {
+ }
+
+ @Override
+ public void write(LittleEndianByteArrayOutputStream leos) {
+ }
+
+ @Override
+ public XOREncryptionHeader clone() throws CloneNotSupportedException {
+ return (XOREncryptionHeader)super.clone();
+ }
+}
diff --git a/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionInfoBuilder.java b/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionInfoBuilder.java
new file mode 100644
index 000000000..9fcaf8b35
--- /dev/null
+++ b/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionInfoBuilder.java
@@ -0,0 +1,62 @@
+/* ====================================================================
+ 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.xor;
+
+import java.io.IOException;
+
+import org.apache.poi.poifs.crypt.ChainingMode;
+import org.apache.poi.poifs.crypt.CipherAlgorithm;
+import org.apache.poi.poifs.crypt.Decryptor;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.poifs.crypt.EncryptionInfoBuilder;
+import org.apache.poi.poifs.crypt.Encryptor;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.poi.util.LittleEndianInput;
+
+public class XOREncryptionInfoBuilder implements EncryptionInfoBuilder {
+
+ public XOREncryptionInfoBuilder() {
+ }
+
+ @Override
+ public void initialize(EncryptionInfo info, LittleEndianInput dis)
+ throws IOException {
+ info.setHeader(new XOREncryptionHeader());
+ info.setVerifier(new XOREncryptionVerifier(dis));
+ Decryptor dec = new XORDecryptor();
+ dec.setEncryptionInfo(info);
+ info.setDecryptor(dec);
+ Encryptor enc = new XOREncryptor();
+ enc.setEncryptionInfo(info);
+ info.setEncryptor(enc);
+ }
+
+ @Override
+ public void initialize(EncryptionInfo info,
+ CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm,
+ int keyBits, int blockSize, ChainingMode chainingMode) {
+ info.setHeader(new XOREncryptionHeader());
+ info.setVerifier(new XOREncryptionVerifier());
+ Decryptor dec = new XORDecryptor();
+ dec.setEncryptionInfo(info);
+ info.setDecryptor(dec);
+ Encryptor enc = new XOREncryptor();
+ enc.setEncryptionInfo(info);
+ info.setEncryptor(enc);
+ }
+}
diff --git a/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionVerifier.java b/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionVerifier.java
new file mode 100644
index 000000000..388e3d872
--- /dev/null
+++ b/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionVerifier.java
@@ -0,0 +1,71 @@
+/* ====================================================================
+ 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.xor;
+
+import org.apache.poi.poifs.crypt.EncryptionVerifier;
+import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+import org.apache.poi.util.LittleEndianInput;
+
+public class XOREncryptionVerifier extends EncryptionVerifier implements EncryptionRecord, Cloneable {
+
+ protected XOREncryptionVerifier() {
+ setEncryptedKey(new byte[2]);
+ setEncryptedVerifier(new byte[2]);
+ }
+
+ protected XOREncryptionVerifier(LittleEndianInput is) {
+ /**
+ * 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.
+ */
+ byte key[] = new byte[2];
+ is.readFully(key);
+ setEncryptedKey(key);
+
+ /**
+ * verificationBytes (2 bytes): An unsigned integer that specifies
+ * the password verification identifier.
+ */
+ byte verifier[] = new byte[2];
+ is.readFully(verifier);
+ setEncryptedVerifier(verifier);
+ }
+
+ @Override
+ public void write(LittleEndianByteArrayOutputStream bos) {
+ bos.write(getEncryptedKey());
+ bos.write(getEncryptedVerifier());
+ }
+
+ @Override
+ public XOREncryptionVerifier clone() throws CloneNotSupportedException {
+ return (XOREncryptionVerifier)super.clone();
+ }
+
+ @Override
+ protected void setEncryptedVerifier(byte[] encryptedVerifier) {
+ super.setEncryptedVerifier(encryptedVerifier);
+ }
+
+ @Override
+ protected void setEncryptedKey(byte[] encryptedKey) {
+ super.setEncryptedKey(encryptedKey);
+ }
+}
diff --git a/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptor.java b/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptor.java
new file mode 100644
index 000000000..4a0b48801
--- /dev/null
+++ b/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptor.java
@@ -0,0 +1,186 @@
+/* ====================================================================
+ 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.xor;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.util.BitSet;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.poi.poifs.crypt.ChunkedCipherOutputStream;
+import org.apache.poi.poifs.crypt.CryptoFunctions;
+import org.apache.poi.poifs.crypt.Encryptor;
+import org.apache.poi.poifs.filesystem.DirectoryNode;
+import org.apache.poi.util.LittleEndian;
+
+public class XOREncryptor extends Encryptor implements Cloneable {
+ protected XOREncryptor() {
+ }
+
+ @Override
+ public void confirmPassword(String password) {
+ int keyComp = CryptoFunctions.createXorKey1(password);
+ int verifierComp = CryptoFunctions.createXorVerifier1(password);
+ byte xorArray[] = CryptoFunctions.createXorArray1(password);
+
+ byte shortBuf[] = new byte[2];
+ XOREncryptionVerifier ver = (XOREncryptionVerifier)getEncryptionInfo().getVerifier();
+ LittleEndian.putUShort(shortBuf, 0, keyComp);
+ ver.setEncryptedKey(shortBuf);
+ LittleEndian.putUShort(shortBuf, 0, verifierComp);
+ ver.setEncryptedVerifier(shortBuf);
+ setSecretKey(new SecretKeySpec(xorArray, "XOR"));
+ }
+
+ @Override
+ public void confirmPassword(String password, byte keySpec[],
+ byte keySalt[], byte verifier[], byte verifierSalt[],
+ byte integritySalt[]) {
+ confirmPassword(password);
+ }
+
+ @Override
+ public OutputStream getDataStream(DirectoryNode dir)
+ throws IOException, GeneralSecurityException {
+ OutputStream countStream = new XORCipherOutputStream(dir);
+ return countStream;
+ }
+
+ @Override
+ public XORCipherOutputStream getDataStream(OutputStream stream, int initialOffset)
+ throws IOException, GeneralSecurityException {
+ return new XORCipherOutputStream(stream, initialOffset);
+ }
+
+ protected int getKeySizeInBytes() {
+ return -1;
+ }
+
+ @Override
+ public void setChunkSize(int chunkSize) {
+ // chunkSize is irrelevant
+ }
+
+ protected void createEncryptionInfoEntry(DirectoryNode dir) throws IOException {
+ }
+
+ @Override
+ public XOREncryptor clone() throws CloneNotSupportedException {
+ return (XOREncryptor)super.clone();
+ }
+
+ private class XORCipherOutputStream extends ChunkedCipherOutputStream {
+ private final int _initialOffset;
+ private int _recordStart = 0;
+ private int _recordEnd = 0;
+ private boolean _isPlain = false;
+
+ public XORCipherOutputStream(OutputStream stream, int initialPos) throws IOException, GeneralSecurityException {
+ super(stream, -1);
+ _initialOffset = initialPos;
+ }
+
+ public XORCipherOutputStream(DirectoryNode dir) throws IOException, GeneralSecurityException {
+ super(dir, -1);
+ _initialOffset = 0;
+ }
+
+ @Override
+ protected Cipher initCipherForBlock(Cipher cipher, int block, boolean lastChunk)
+ throws GeneralSecurityException {
+ return XORDecryptor.initCipherForBlock(cipher, block, getEncryptionInfo(), getSecretKey(), Cipher.ENCRYPT_MODE);
+ }
+
+ @Override
+ protected void calculateChecksum(File file, int i) {
+ }
+
+ @Override
+ protected void createEncryptionInfoEntry(DirectoryNode dir, File tmpFile)
+ throws IOException, GeneralSecurityException {
+ XOREncryptor.this.createEncryptionInfoEntry(dir);
+ }
+
+ @Override
+ public void setNextRecordSize(int recordSize, boolean isPlain) {
+ if (_recordEnd > 0 && !_isPlain) {
+ // encrypt last record
+ invokeCipher((int)getPos(), true);
+ }
+ _recordStart = (int)getTotalPos()+4;
+ _recordEnd = _recordStart+recordSize;
+ _isPlain = isPlain;
+ }
+
+ @Override
+ public void flush() throws IOException {
+ setNextRecordSize(0, true);
+ super.flush();
+ }
+
+ @Override
+ protected int invokeCipher(int posInChunk, boolean doFinal) {
+ if (posInChunk == 0) {
+ return 0;
+ }
+
+ final int start = Math.max(posInChunk-(_recordEnd-_recordStart), 0);
+
+ final BitSet plainBytes = getPlainByteFlags();
+ final byte xorArray[] = getEncryptionInfo().getEncryptor().getSecretKey().getEncoded();
+ final byte chunk[] = getChunk();
+ final byte plain[] = (plainBytes.isEmpty()) ? null : chunk.clone();
+
+ /*
+ * From: http://social.msdn.microsoft.com/Forums/en-US/3dadbed3-0e68-4f11-8b43-3a2328d9ebd5
+ *
+ * The initial value for XorArrayIndex is as follows:
+ * XorArrayIndex = (FileOffset + Data.Length) % 16
+ *
+ * The FileOffset variable in this context is the stream offset into the Workbook stream at
+ * the time we are about to write each of the bytes of the record data.
+ * This (the value) is then incremented after each byte is written.
+ */
+ // ... also need to handle invocation in case of a filled chunk
+ int xorArrayIndex = _recordEnd+(start-_recordStart);
+
+ for (int i=start; i < posInChunk; i++) {
+ byte value = chunk[i];
+ value ^= xorArray[(xorArrayIndex++) & 0x0F];
+ value = rotateLeft(value, 8-3);
+ chunk[i] = value;
+ }
+
+ for (int i = plainBytes.nextSetBit(start); i >= 0 && i < posInChunk; i = plainBytes.nextSetBit(i+1)) {
+ chunk[i] = plain[i];
+ }
+
+ return posInChunk;
+ }
+
+ private byte rotateLeft(byte bits, int shift) {
+ return (byte)(((bits & 0xff) << shift) | ((bits & 0xff) >>> (8 - shift)));
+ }
+
+
+ }
+}
diff --git a/src/java/org/apache/poi/poifs/filesystem/DocumentInputStream.java b/src/java/org/apache/poi/poifs/filesystem/DocumentInputStream.java
index 672a4aa11..ffd6a1a85 100644
--- a/src/java/org/apache/poi/poifs/filesystem/DocumentInputStream.java
+++ b/src/java/org/apache/poi/poifs/filesystem/DocumentInputStream.java
@@ -189,4 +189,9 @@ public class DocumentInputStream extends InputStream implements LittleEndianInpu
int i = readInt();
return i & 0xFFFFFFFFL;
}
+
+ @Override
+ public void readPlain(byte[] buf, int off, int len) {
+ readFully(buf, off, len);
+ }
}
diff --git a/src/java/org/apache/poi/util/LittleEndianByteArrayInputStream.java b/src/java/org/apache/poi/util/LittleEndianByteArrayInputStream.java
index 54388e151..2c5fe70b7 100644
--- a/src/java/org/apache/poi/util/LittleEndianByteArrayInputStream.java
+++ b/src/java/org/apache/poi/util/LittleEndianByteArrayInputStream.java
@@ -17,103 +17,96 @@
package org.apache.poi.util;
+import java.io.ByteArrayInputStream;
+
/**
* Adapts a plain byte array to {@link LittleEndianInput}
- *
- * @author Josh Micich
*/
-public final class LittleEndianByteArrayInputStream implements LittleEndianInput {
- private final byte[] _buf;
- private final int _endIndex;
- private int _readIndex;
-
+public final class LittleEndianByteArrayInputStream extends ByteArrayInputStream implements LittleEndianInput {
public LittleEndianByteArrayInputStream(byte[] buf, int startOffset, int maxReadLen) { // NOSONAR
- _buf = buf;
- _readIndex = startOffset;
- _endIndex = startOffset + maxReadLen;
+ super(buf, startOffset, maxReadLen);
}
+
public LittleEndianByteArrayInputStream(byte[] buf, int startOffset) {
- this(buf, startOffset, buf.length - startOffset);
+ super(buf, startOffset, buf.length - startOffset);
}
+
public LittleEndianByteArrayInputStream(byte[] buf) {
- this(buf, 0, buf.length);
+ super(buf);
}
- public int available() {
- return _endIndex - _readIndex;
- }
private void checkPosition(int i) {
- if (i > _endIndex - _readIndex) {
+ if (i > count - pos) {
throw new RuntimeException("Buffer overrun");
}
}
public int getReadIndex() {
- return _readIndex;
+ return pos;
}
- public byte readByte() {
+
+ @Override
+ public byte readByte() {
checkPosition(1);
- return _buf[_readIndex++];
+ return (byte)read();
}
- public int readInt() {
- checkPosition(4);
- int i = _readIndex;
-
- int b0 = _buf[i++] & 0xFF;
- int b1 = _buf[i++] & 0xFF;
- int b2 = _buf[i++] & 0xFF;
- int b3 = _buf[i++] & 0xFF;
- _readIndex = i;
- return (b3 << 24) + (b2 << 16) + (b1 << 8) + (b0 << 0);
+ @Override
+ public int readInt() {
+ final int size = LittleEndianConsts.INT_SIZE;
+ checkPosition(size);
+ int le = LittleEndian.getInt(buf, pos);
+ super.skip(size);
+ return le;
}
- public long readLong() {
- checkPosition(8);
- int i = _readIndex;
- int b0 = _buf[i++] & 0xFF;
- int b1 = _buf[i++] & 0xFF;
- int b2 = _buf[i++] & 0xFF;
- int b3 = _buf[i++] & 0xFF;
- int b4 = _buf[i++] & 0xFF;
- int b5 = _buf[i++] & 0xFF;
- int b6 = _buf[i++] & 0xFF;
- int b7 = _buf[i++] & 0xFF;
- _readIndex = i;
- return (((long)b7 << 56) +
- ((long)b6 << 48) +
- ((long)b5 << 40) +
- ((long)b4 << 32) +
- ((long)b3 << 24) +
- (b2 << 16) +
- (b1 << 8) +
- (b0 << 0));
+ @Override
+ public long readLong() {
+ final int size = LittleEndianConsts.LONG_SIZE;
+ checkPosition(size);
+ long le = LittleEndian.getLong(buf, pos);
+ super.skip(size);
+ return le;
}
- public short readShort() {
+
+ @Override
+ public short readShort() {
return (short)readUShort();
}
- public int readUByte() {
- checkPosition(1);
- return _buf[_readIndex++] & 0xFF;
- }
- public int readUShort() {
- checkPosition(2);
- int i = _readIndex;
- int b0 = _buf[i++] & 0xFF;
- int b1 = _buf[i++] & 0xFF;
- _readIndex = i;
- return (b1 << 8) + (b0 << 0);
+ @Override
+ public int readUByte() {
+ return readByte() & 0xFF;
}
- public void readFully(byte[] buf, int off, int len) {
+
+ @Override
+ public int readUShort() {
+ final int size = LittleEndianConsts.SHORT_SIZE;
+ checkPosition(size);
+ int le = LittleEndian.getUShort(buf, pos);
+ super.skip(size);
+ return le;
+ }
+
+ @Override
+ public double readDouble() {
+ return Double.longBitsToDouble(readLong());
+ }
+
+ @Override
+ public void readFully(byte[] buffer, int off, int len) {
checkPosition(len);
- System.arraycopy(_buf, _readIndex, buf, off, len);
- _readIndex+=len;
+ read(buffer, off, len);
}
- public void readFully(byte[] buf) {
- readFully(buf, 0, buf.length);
- }
- public double readDouble() {
- return Double.longBitsToDouble(readLong());
+
+ @Override
+ public void readFully(byte[] buffer) {
+ checkPosition(buffer.length);
+ read(buffer, 0, buffer.length);
}
+
+ @Override
+ public void readPlain(byte[] buf, int off, int len) {
+ readFully(buf, off, len);
+ }
}
diff --git a/src/java/org/apache/poi/util/LittleEndianByteArrayOutputStream.java b/src/java/org/apache/poi/util/LittleEndianByteArrayOutputStream.java
index 081309cc2..fe9949f1a 100644
--- a/src/java/org/apache/poi/util/LittleEndianByteArrayOutputStream.java
+++ b/src/java/org/apache/poi/util/LittleEndianByteArrayOutputStream.java
@@ -17,28 +17,26 @@
package org.apache.poi.util;
+import java.io.OutputStream;
/**
- * Adapts a plain byte array to {@link LittleEndianOutput}
- *
- *
- * @author Josh Micich
+ * Adapts a plain byte array to {@link LittleEndianOutput}
*/
-public final class LittleEndianByteArrayOutputStream implements LittleEndianOutput, DelayableLittleEndianOutput {
+public final class LittleEndianByteArrayOutputStream extends OutputStream implements LittleEndianOutput, DelayableLittleEndianOutput {
private final byte[] _buf;
private final int _endIndex;
private int _writeIndex;
public LittleEndianByteArrayOutputStream(byte[] buf, int startOffset, int maxWriteLen) { // NOSONAR
if (startOffset < 0 || startOffset > buf.length) {
- throw new IllegalArgumentException("Specified startOffset (" + startOffset
+ throw new IllegalArgumentException("Specified startOffset (" + startOffset
+ ") is out of allowable range (0.." + buf.length + ")");
}
_buf = buf;
_writeIndex = startOffset;
_endIndex = startOffset + maxWriteLen;
if (_endIndex < startOffset || _endIndex > buf.length) {
- throw new IllegalArgumentException("calculated end index (" + _endIndex
+ throw new IllegalArgumentException("calculated end index (" + _endIndex
+ ") is out of allowable range (" + _writeIndex + ".." + buf.length + ")");
}
}
@@ -52,16 +50,19 @@ public final class LittleEndianByteArrayOutputStream implements LittleEndianOutp
}
}
- public void writeByte(int v) {
+ @Override
+ public void writeByte(int v) {
checkPosition(1);
_buf[_writeIndex++] = (byte)v;
}
- public void writeDouble(double v) {
+ @Override
+ public void writeDouble(double v) {
writeLong(Double.doubleToLongBits(v));
}
- public void writeInt(int v) {
+ @Override
+ public void writeInt(int v) {
checkPosition(4);
int i = _writeIndex;
_buf[i++] = (byte)((v >>> 0) & 0xFF);
@@ -71,33 +72,47 @@ public final class LittleEndianByteArrayOutputStream implements LittleEndianOutp
_writeIndex = i;
}
- public void writeLong(long v) {
+ @Override
+ public void writeLong(long v) {
writeInt((int)(v >> 0));
writeInt((int)(v >> 32));
}
- public void writeShort(int v) {
+ @Override
+ public void writeShort(int v) {
checkPosition(2);
int i = _writeIndex;
_buf[i++] = (byte)((v >>> 0) & 0xFF);
_buf[i++] = (byte)((v >>> 8) & 0xFF);
_writeIndex = i;
}
- public void write(byte[] b) {
+
+ @Override
+ public void write(int b) {
+ writeByte(b);
+ }
+
+ @Override
+ public void write(byte[] b) {
int len = b.length;
checkPosition(len);
System.arraycopy(b, 0, _buf, _writeIndex, len);
_writeIndex += len;
}
- public void write(byte[] b, int offset, int len) {
+
+ @Override
+ public void write(byte[] b, int offset, int len) {
checkPosition(len);
System.arraycopy(b, offset, _buf, _writeIndex, len);
_writeIndex += len;
}
+
public int getWriteIndex() {
return _writeIndex;
}
- public LittleEndianOutput createDelayedOutput(int size) {
+
+ @Override
+ public LittleEndianOutput createDelayedOutput(int size) {
checkPosition(size);
LittleEndianOutput result = new LittleEndianByteArrayOutputStream(_buf, _writeIndex, size);
_writeIndex += size;
diff --git a/src/java/org/apache/poi/util/LittleEndianInput.java b/src/java/org/apache/poi/util/LittleEndianInput.java
index d8db24797..c140c275d 100644
--- a/src/java/org/apache/poi/util/LittleEndianInput.java
+++ b/src/java/org/apache/poi/util/LittleEndianInput.java
@@ -16,10 +16,7 @@
==================================================================== */
package org.apache.poi.util;
-/**
- *
- * @author Josh Micich
- */
+
public interface LittleEndianInput {
int available();
byte readByte();
@@ -31,4 +28,14 @@ public interface LittleEndianInput {
double readDouble();
void readFully(byte[] buf);
void readFully(byte[] buf, int off, int len);
+
+ /**
+ * Usually acts the same as {@link #readFully(byte[], int, int)}, but
+ * for an encrypted stream the raw (unencrypted) data is filled
+ *
+ * @param buf the byte array to receive the bytes
+ * @param off the start offset into the byte array
+ * @param len the amount of bytes to fill
+ */
+ void readPlain(byte[] buf, int off, int len);
}
diff --git a/src/java/org/apache/poi/util/LittleEndianInputStream.java b/src/java/org/apache/poi/util/LittleEndianInputStream.java
index da42caf3d..3062c50ff 100644
--- a/src/java/org/apache/poi/util/LittleEndianInputStream.java
+++ b/src/java/org/apache/poi/util/LittleEndianInputStream.java
@@ -34,7 +34,8 @@ public class LittleEndianInputStream extends FilterInputStream implements Little
super(is);
}
- public int available() {
+ @Override
+ public int available() {
try {
return super.available();
} catch (IOException e) {
@@ -42,11 +43,13 @@ public class LittleEndianInputStream extends FilterInputStream implements Little
}
}
- public byte readByte() {
+ @Override
+ public byte readByte() {
return (byte)readUByte();
}
- public int readUByte() {
+ @Override
+ public int readUByte() {
byte buf[] = new byte[1];
try {
checkEOF(read(buf), 1);
@@ -56,11 +59,13 @@ public class LittleEndianInputStream extends FilterInputStream implements Little
return LittleEndian.getUByte(buf);
}
- public double readDouble() {
+ @Override
+ public double readDouble() {
return Double.longBitsToDouble(readLong());
}
- public int readInt() {
+ @Override
+ public int readInt() {
byte buf[] = new byte[LittleEndianConsts.INT_SIZE];
try {
checkEOF(read(buf), buf.length);
@@ -82,7 +87,8 @@ public class LittleEndianInputStream extends FilterInputStream implements Little
return retNum & 0x00FFFFFFFFL;
}
- public long readLong() {
+ @Override
+ public long readLong() {
byte buf[] = new byte[LittleEndianConsts.LONG_SIZE];
try {
checkEOF(read(buf), LittleEndianConsts.LONG_SIZE);
@@ -92,11 +98,13 @@ public class LittleEndianInputStream extends FilterInputStream implements Little
return LittleEndian.getLong(buf);
}
- public short readShort() {
+ @Override
+ public short readShort() {
return (short)readUShort();
}
- public int readUShort() {
+ @Override
+ public int readUShort() {
byte buf[] = new byte[LittleEndianConsts.SHORT_SIZE];
try {
checkEOF(read(buf), LittleEndianConsts.SHORT_SIZE);
@@ -112,15 +120,22 @@ public class LittleEndianInputStream extends FilterInputStream implements Little
}
}
- public void readFully(byte[] buf) {
+ @Override
+ public void readFully(byte[] buf) {
readFully(buf, 0, buf.length);
}
- public void readFully(byte[] buf, int off, int len) {
+ @Override
+ public void readFully(byte[] buf, int off, int len) {
try {
checkEOF(read(buf, off, len), len);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
+
+ @Override
+ public void readPlain(byte[] buf, int off, int len) {
+ readFully(buf, off, len);
+ }
}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java
index f8c9bb551..480137ef8 100644
--- a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java
@@ -45,7 +45,7 @@ import org.apache.poi.poifs.crypt.CipherAlgorithm;
import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.poifs.crypt.Decryptor;
import org.apache.poi.poifs.crypt.EncryptionHeader;
-import org.apache.poi.poifs.crypt.EncryptionInfoBuilder;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
import org.apache.poi.poifs.crypt.EncryptionVerifier;
import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.crypt.agile.AgileEncryptionVerifier.AgileCertificateEntry;
@@ -56,14 +56,14 @@ import org.apache.poi.util.LittleEndian;
/**
* Decryptor implementation for Agile Encryption
*/
-public class AgileDecryptor extends Decryptor {
+public class AgileDecryptor extends Decryptor implements Cloneable {
private long _length = -1;
- protected static final byte[] kVerifierInputBlock;
- protected static final byte[] kHashedVerifierBlock;
- protected static final byte[] kCryptoKeyBlock;
- protected static final byte[] kIntegrityKeyBlock;
- protected static final byte[] kIntegrityValueBlock;
+ /* package */ static final byte[] kVerifierInputBlock;
+ /* package */ static final byte[] kHashedVerifierBlock;
+ /* package */ static final byte[] kCryptoKeyBlock;
+ /* package */ static final byte[] kIntegrityKeyBlock;
+ /* package */ static final byte[] kIntegrityValueBlock;
static {
kVerifierInputBlock =
@@ -83,16 +83,16 @@ public class AgileDecryptor extends Decryptor {
(byte)0xb2, (byte)0x2c, (byte)0x84, (byte)0x33 };
}
- protected AgileDecryptor(AgileEncryptionInfoBuilder builder) {
- super(builder);
+ protected AgileDecryptor() {
}
/**
* set decryption password
*/
+ @Override
public boolean verifyPassword(String password) throws GeneralSecurityException {
- AgileEncryptionVerifier ver = (AgileEncryptionVerifier)builder.getVerifier();
- AgileEncryptionHeader header = (AgileEncryptionHeader)builder.getHeader();
+ AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
+ AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
HashAlgorithm hashAlgo = header.getHashAlgorithmEx();
CipherAlgorithm cipherAlgo = header.getCipherAlgorithm();
int blockSize = header.getBlockSize();
@@ -113,7 +113,7 @@ public class AgileDecryptor extends Decryptor {
* blockSize bytes.
* 4. Use base64 to encode the result of step 3.
*/
- byte verfierInputEnc[] = hashInput(builder, pwHash, kVerifierInputBlock, ver.getEncryptedVerifier(), Cipher.DECRYPT_MODE);
+ byte verfierInputEnc[] = hashInput(getEncryptionInfo(), pwHash, kVerifierInputBlock, ver.getEncryptedVerifier(), Cipher.DECRYPT_MODE);
setVerifier(verfierInputEnc);
MessageDigest hashMD = getMessageDigest(hashAlgo);
byte[] verifierHash = hashMD.digest(verfierInputEnc);
@@ -130,7 +130,7 @@ public class AgileDecryptor extends Decryptor {
* blockSize bytes, pad the hash value with 0x00 to an integral multiple of blockSize bytes.
* 4. Use base64 to encode the result of step 3.
*/
- byte verifierHashDec[] = hashInput(builder, pwHash, kHashedVerifierBlock, ver.getEncryptedVerifierHash(), Cipher.DECRYPT_MODE);
+ byte verifierHashDec[] = hashInput(getEncryptionInfo(), pwHash, kHashedVerifierBlock, ver.getEncryptedVerifierHash(), Cipher.DECRYPT_MODE);
verifierHashDec = getBlock0(verifierHashDec, hashAlgo.hashSize);
/**
@@ -146,7 +146,7 @@ public class AgileDecryptor extends Decryptor {
* blockSize bytes.
* 4. Use base64 to encode the result of step 3.
*/
- byte keyspec[] = hashInput(builder, pwHash, kCryptoKeyBlock, ver.getEncryptedKey(), Cipher.DECRYPT_MODE);
+ byte keyspec[] = hashInput(getEncryptionInfo(), pwHash, kCryptoKeyBlock, ver.getEncryptedKey(), Cipher.DECRYPT_MODE);
keyspec = getBlock0(keyspec, keySize);
SecretKeySpec secretKey = new SecretKeySpec(keyspec, ver.getCipherAlgorithm().jceId);
@@ -204,8 +204,8 @@ public class AgileDecryptor extends Decryptor {
* @throws GeneralSecurityException
*/
public boolean verifyPassword(KeyPair keyPair, X509Certificate x509) throws GeneralSecurityException {
- AgileEncryptionVerifier ver = (AgileEncryptionVerifier)builder.getVerifier();
- AgileEncryptionHeader header = (AgileEncryptionHeader)builder.getHeader();
+ AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
+ AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
HashAlgorithm hashAlgo = header.getHashAlgorithmEx();
CipherAlgorithm cipherAlgo = header.getCipherAlgorithm();
int blockSize = header.getBlockSize();
@@ -217,7 +217,9 @@ public class AgileDecryptor extends Decryptor {
break;
}
}
- if (ace == null) return false;
+ if (ace == null) {
+ return false;
+ }
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
@@ -255,9 +257,9 @@ public class AgileDecryptor extends Decryptor {
return fillSize;
}
- protected static byte[] hashInput(EncryptionInfoBuilder builder, byte pwHash[], byte blockKey[], byte inputKey[], int cipherMode) {
- EncryptionVerifier ver = builder.getVerifier();
- AgileDecryptor dec = (AgileDecryptor)builder.getDecryptor();
+ protected static byte[] hashInput(EncryptionInfo encryptionInfo, byte pwHash[], byte blockKey[], byte inputKey[], int cipherMode) {
+ EncryptionVerifier ver = encryptionInfo.getVerifier();
+ AgileDecryptor dec = (AgileDecryptor)encryptionInfo.getDecryptor();
int keySize = dec.getKeySizeInBytes();
int blockSize = dec.getBlockSizeInBytes();
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
@@ -278,6 +280,7 @@ public class AgileDecryptor extends Decryptor {
}
}
+ @Override
@SuppressWarnings("resource")
public InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException {
DocumentInputStream dis = dir.createDocumentInputStream(DEFAULT_POIFS_ENTRY);
@@ -285,17 +288,20 @@ public class AgileDecryptor extends Decryptor {
return new AgileCipherInputStream(dis, _length);
}
+ @Override
public long getLength(){
- if(_length == -1) throw new IllegalStateException("EcmaDecryptor.getDataStream() was not called");
+ if(_length == -1) {
+ throw new IllegalStateException("EcmaDecryptor.getDataStream() was not called");
+ }
return _length;
}
- protected static Cipher initCipherForBlock(Cipher existing, int block, boolean lastChunk, EncryptionInfoBuilder builder, SecretKey skey, int encryptionMode)
+ protected static Cipher initCipherForBlock(Cipher existing, int block, boolean lastChunk, EncryptionInfo encryptionInfo, SecretKey skey, int encryptionMode)
throws GeneralSecurityException {
- EncryptionHeader header = builder.getHeader();
- if (existing == null || lastChunk) {
- String padding = (lastChunk ? "PKCS5Padding" : "NoPadding");
+ EncryptionHeader header = encryptionInfo.getHeader();
+ String padding = (lastChunk ? "PKCS5Padding" : "NoPadding");
+ if (existing == null || !existing.getAlgorithm().endsWith(padding)) {
existing = getCipher(skey, header.getCipherAlgorithm(), header.getChainingMode(), header.getKeySalt(), encryptionMode, padding);
}
@@ -339,9 +345,15 @@ public class AgileDecryptor extends Decryptor {
// TODO: calculate integrity hmac while reading the stream
// for a post-validation of the data
+ @Override
protected Cipher initCipherForBlock(Cipher cipher, int block)
throws GeneralSecurityException {
- return AgileDecryptor.initCipherForBlock(cipher, block, false, builder, getSecretKey(), Cipher.DECRYPT_MODE);
+ return AgileDecryptor.initCipherForBlock(cipher, block, false, getEncryptionInfo(), getSecretKey(), Cipher.DECRYPT_MODE);
}
}
+
+ @Override
+ public AgileDecryptor clone() throws CloneNotSupportedException {
+ return (AgileDecryptor)super.clone();
+ }
}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionHeader.java b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionHeader.java
index a5fb14428..88bccabf6 100644
--- a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionHeader.java
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionHeader.java
@@ -27,7 +27,7 @@ import com.microsoft.schemas.office.x2006.encryption.CTKeyData;
import com.microsoft.schemas.office.x2006.encryption.EncryptionDocument;
import com.microsoft.schemas.office.x2006.encryption.STCipherChaining;
-public class AgileEncryptionHeader extends EncryptionHeader {
+public class AgileEncryptionHeader extends EncryptionHeader implements Cloneable {
private byte encryptedHmacKey[], encryptedHmacValue[];
public AgileEncryptionHeader(String descriptor) {
@@ -99,6 +99,7 @@ public class AgileEncryptionHeader extends EncryptionHeader {
}
// make method visible for this package
+ @Override
protected void setKeySalt(byte salt[]) {
if (salt == null || salt.length != getBlockSize()) {
throw new EncryptedDocumentException("invalid verifier salt");
@@ -121,4 +122,13 @@ public class AgileEncryptionHeader extends EncryptionHeader {
protected void setEncryptedHmacValue(byte[] encryptedHmacValue) {
this.encryptedHmacValue = (encryptedHmacValue == null) ? null : encryptedHmacValue.clone();
}
+
+ @Override
+ public AgileEncryptionHeader clone() throws CloneNotSupportedException {
+ AgileEncryptionHeader other = (AgileEncryptionHeader)super.clone();
+ other.encryptedHmacKey = (encryptedHmacKey == null) ? null : encryptedHmacKey.clone();
+ other.encryptedHmacValue = (encryptedHmacValue == null) ? null : encryptedHmacValue.clone();
+ return other;
+ }
+
}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionInfoBuilder.java b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionInfoBuilder.java
index ecf7fe8e6..acfa01581 100644
--- a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionInfoBuilder.java
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionInfoBuilder.java
@@ -35,30 +35,24 @@ import com.microsoft.schemas.office.x2006.encryption.EncryptionDocument;
public class AgileEncryptionInfoBuilder implements EncryptionInfoBuilder {
- EncryptionInfo info;
- AgileEncryptionHeader header;
- AgileEncryptionVerifier verifier;
- AgileDecryptor decryptor;
- AgileEncryptor encryptor;
-
@Override
- public void initialize(EncryptionInfo ei, LittleEndianInput dis) throws IOException {
- this.info = ei;
-
+ public void initialize(EncryptionInfo info, LittleEndianInput dis) throws IOException {
EncryptionDocument ed = parseDescriptor((InputStream)dis);
- header = new AgileEncryptionHeader(ed);
- verifier = new AgileEncryptionVerifier(ed);
- if (ei.getVersionMajor() == EncryptionMode.agile.versionMajor
- && ei.getVersionMinor() == EncryptionMode.agile.versionMinor) {
- decryptor = new AgileDecryptor(this);
- encryptor = new AgileEncryptor(this);
+ info.setHeader(new AgileEncryptionHeader(ed));
+ info.setVerifier(new AgileEncryptionVerifier(ed));
+ if (info.getVersionMajor() == EncryptionMode.agile.versionMajor
+ && info.getVersionMinor() == EncryptionMode.agile.versionMinor) {
+ AgileDecryptor dec = new AgileDecryptor();
+ dec.setEncryptionInfo(info);
+ info.setDecryptor(dec);
+ AgileEncryptor enc = new AgileEncryptor();
+ enc.setEncryptionInfo(info);
+ info.setEncryptor(enc);
}
}
@Override
- public void initialize(EncryptionInfo ei, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
- this.info = ei;
-
+ public void initialize(EncryptionInfo info, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
if (cipherAlgorithm == null) {
cipherAlgorithm = CipherAlgorithm.aes128;
}
@@ -87,30 +81,14 @@ public class AgileEncryptionInfoBuilder implements EncryptionInfoBuilder {
if (!found) {
throw new EncryptedDocumentException("KeySize "+keyBits+" not allowed for Cipher "+cipherAlgorithm.toString());
}
- header = new AgileEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
- verifier = new AgileEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
- decryptor = new AgileDecryptor(this);
- encryptor = new AgileEncryptor(this);
- }
-
- public AgileEncryptionHeader getHeader() {
- return header;
- }
-
- public AgileEncryptionVerifier getVerifier() {
- return verifier;
- }
-
- public AgileDecryptor getDecryptor() {
- return decryptor;
- }
-
- public AgileEncryptor getEncryptor() {
- return encryptor;
- }
-
- protected EncryptionInfo getInfo() {
- return info;
+ info.setHeader(new AgileEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
+ info.setVerifier(new AgileEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
+ AgileDecryptor dec = new AgileDecryptor();
+ dec.setEncryptionInfo(info);
+ info.setDecryptor(dec);
+ AgileEncryptor enc = new AgileEncryptor();
+ enc.setEncryptionInfo(info);
+ info.setEncryptor(enc);
}
protected static EncryptionDocument parseDescriptor(String descriptor) {
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionVerifier.java b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionVerifier.java
index e2910431a..53d4cd6ed 100644
--- a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionVerifier.java
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionVerifier.java
@@ -39,7 +39,7 @@ import com.microsoft.schemas.office.x2006.keyEncryptor.password.CTPasswordKeyEnc
/**
* Used when checking if a key is valid for a document
*/
-public class AgileEncryptionVerifier extends EncryptionVerifier {
+public class AgileEncryptionVerifier extends EncryptionVerifier implements Cloneable {
public static class AgileCertificateEntry {
X509Certificate x509;
@@ -87,8 +87,9 @@ public class AgileEncryptionVerifier extends EncryptionVerifier {
setEncryptedVerifierHash(keyData.getEncryptedVerifierHashValue());
int saltSize = keyData.getSaltSize();
- if (saltSize != getSalt().length)
+ if (saltSize != getSalt().length) {
throw new EncryptedDocumentException("Invalid salt size");
+ }
switch (keyData.getCipherChaining().intValue()) {
case STCipherChaining.INT_CHAINING_MODE_CBC:
@@ -101,7 +102,9 @@ public class AgileEncryptionVerifier extends EncryptionVerifier {
throw new EncryptedDocumentException("Unsupported chaining mode - "+keyData.getCipherChaining().toString());
}
- if (!encList.hasNext()) return;
+ if (!encList.hasNext()) {
+ return;
+ }
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
@@ -125,6 +128,7 @@ public class AgileEncryptionVerifier extends EncryptionVerifier {
setSpinCount(100000); // TODO: use parameter
}
+ @Override
protected void setSalt(byte salt[]) {
if (salt == null || salt.length != getCipherAlgorithm().blockSize) {
throw new EncryptedDocumentException("invalid verifier salt");
@@ -133,16 +137,19 @@ public class AgileEncryptionVerifier extends EncryptionVerifier {
}
// make method visible for this package
+ @Override
protected void setEncryptedVerifier(byte encryptedVerifier[]) {
super.setEncryptedVerifier(encryptedVerifier);
}
// make method visible for this package
+ @Override
protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) {
super.setEncryptedVerifierHash(encryptedVerifierHash);
}
// make method visible for this package
+ @Override
protected void setEncryptedKey(byte[] encryptedKey) {
super.setEncryptedKey(encryptedKey);
}
@@ -156,4 +163,12 @@ public class AgileEncryptionVerifier extends EncryptionVerifier {
public Listtrue
if record type specified by sid 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);
+ }
+
}
diff --git a/src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.java b/src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.java
index 3a28b81af..382ae2f13 100644
--- a/src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.java
+++ b/src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.java
@@ -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 true
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
diff --git a/src/java/org/apache/poi/hssf/record/crypto/Biff8RC4.java b/src/java/org/apache/poi/hssf/record/crypto/Biff8RC4.java
deleted file mode 100644
index 9d0275fec..000000000
--- a/src/java/org/apache/poi/hssf/record/crypto/Biff8RC4.java
+++ /dev/null
@@ -1,195 +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.hssf.record.crypto;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-import javax.crypto.Cipher;
-import javax.crypto.ShortBufferException;
-
-import org.apache.poi.EncryptedDocumentException;
-import org.apache.poi.hssf.record.BOFRecord;
-import org.apache.poi.hssf.record.FilePassRecord;
-import org.apache.poi.hssf.record.InterfaceHdrRecord;
-
-/**
- * Used for both encrypting and decrypting BIFF8 streams. The internal
- * {@link Cipher} instance is renewed (re-keyed) every 1024 bytes.
- */
-final class Biff8RC4 implements Biff8Cipher {
-
- private static final int RC4_REKEYING_INTERVAL = 1024;
-
- private Cipher _rc4;
-
- /**
- * This field is used to keep track of when to change the {@link Cipher}
- * instance. The change occurs every 1024 bytes. Every byte passed over is
- * counted.
- */
- private int _streamPos;
- private int _nextRC4BlockStart;
- private int _currentKeyIndex;
- private boolean _shouldSkipEncryptionOnCurrentRecord;
- private final Biff8RC4Key _key;
- private ByteBuffer _buffer = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN);
-
- public Biff8RC4(int initialOffset, Biff8RC4Key key) {
- if (initialOffset >= RC4_REKEYING_INTERVAL) {
- throw new RuntimeException("initialOffset (" + initialOffset + ")>"
- + RC4_REKEYING_INTERVAL + " not supported yet");
- }
- _key = key;
- _rc4 = _key.getCipher();
- _streamPos = 0;
- rekeyForNextBlock();
- _streamPos = initialOffset;
- _shouldSkipEncryptionOnCurrentRecord = false;
-
- encryptBytes(new byte[initialOffset], 0, initialOffset);
- }
-
-
- private void rekeyForNextBlock() {
- _currentKeyIndex = _streamPos / RC4_REKEYING_INTERVAL;
- _key.initCipherForBlock(_rc4, _currentKeyIndex);
- _nextRC4BlockStart = (_currentKeyIndex + 1) * RC4_REKEYING_INTERVAL;
- }
-
- private void encryptBytes(byte data[], int offset, final int bytesToRead) {
- if (bytesToRead == 0) return;
-
- if (_shouldSkipEncryptionOnCurrentRecord) {
- // even when encryption is skipped, we need to update the cipher
- byte dataCpy[] = new byte[bytesToRead];
- System.arraycopy(data, offset, dataCpy, 0, bytesToRead);
- data = dataCpy;
- offset = 0;
- }
-
- try {
- _rc4.update(data, offset, bytesToRead, data, offset);
- } catch (ShortBufferException e) {
- throw new EncryptedDocumentException("input buffer too small", e);
- }
- }
-
- public void startRecord(int currentSid) {
- _shouldSkipEncryptionOnCurrentRecord = isNeverEncryptedRecord(currentSid);
- }
-
- /**
- * TODO: Additionally, the lbPlyPos (position_of_BOF) field of the BoundSheet8 record MUST NOT be encrypted.
- *
- * @return true
if record type specified by sid is never encrypted
- */
- private 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;
- }
- return false;
- }
-
- /**
- * Used when BIFF header fields (sid, size) are being read. The internal
- * {@link Cipher} instance must step even when unencrypted bytes are read
- */
- public void skipTwoBytes() {
- xor(_buffer.array(), 0, 2);
- }
-
- public void xor(byte[] buf, int pOffset, int pLen) {
- int nLeftInBlock;
- nLeftInBlock = _nextRC4BlockStart - _streamPos;
- if (pLen <= nLeftInBlock) {
- // simple case - this read does not cross key blocks
- encryptBytes(buf, pOffset, pLen);
- _streamPos += pLen;
- return;
- }
-
- int offset = pOffset;
- int len = pLen;
-
- // start by using the rest of the current block
- if (len > nLeftInBlock) {
- if (nLeftInBlock > 0) {
- encryptBytes(buf, offset, nLeftInBlock);
- _streamPos += nLeftInBlock;
- offset += nLeftInBlock;
- len -= nLeftInBlock;
- }
- rekeyForNextBlock();
- }
- // all full blocks following
- while (len > RC4_REKEYING_INTERVAL) {
- encryptBytes(buf, offset, RC4_REKEYING_INTERVAL);
- _streamPos += RC4_REKEYING_INTERVAL;
- offset += RC4_REKEYING_INTERVAL;
- len -= RC4_REKEYING_INTERVAL;
- rekeyForNextBlock();
- }
- // finish with incomplete block
- encryptBytes(buf, offset, len);
- _streamPos += len;
- }
-
- public int xorByte(int rawVal) {
- _buffer.put(0, (byte)rawVal);
- xor(_buffer.array(), 0, 1);
- return _buffer.get(0);
- }
-
- public int xorShort(int rawVal) {
- _buffer.putShort(0, (short)rawVal);
- xor(_buffer.array(), 0, 2);
- return _buffer.getShort(0);
- }
-
- public int xorInt(int rawVal) {
- _buffer.putInt(0, rawVal);
- xor(_buffer.array(), 0, 4);
- return _buffer.getInt(0);
- }
-
- public long xorLong(long rawVal) {
- _buffer.putLong(0, rawVal);
- xor(_buffer.array(), 0, 8);
- return _buffer.getLong(0);
- }
-
- public void setNextRecordSize(int recordSize) {
- /* no-op */
- }
-}
diff --git a/src/java/org/apache/poi/hssf/record/crypto/Biff8RC4Key.java b/src/java/org/apache/poi/hssf/record/crypto/Biff8RC4Key.java
deleted file mode 100644
index e75f297b0..000000000
--- a/src/java/org/apache/poi/hssf/record/crypto/Biff8RC4Key.java
+++ /dev/null
@@ -1,155 +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.hssf.record.crypto;
-
-import java.security.GeneralSecurityException;
-import java.security.MessageDigest;
-import java.util.Arrays;
-
-import javax.crypto.Cipher;
-import javax.crypto.ShortBufferException;
-import javax.crypto.spec.SecretKeySpec;
-
-import org.apache.poi.EncryptedDocumentException;
-import org.apache.poi.poifs.crypt.CipherAlgorithm;
-import org.apache.poi.poifs.crypt.CryptoFunctions;
-import org.apache.poi.poifs.crypt.HashAlgorithm;
-import org.apache.poi.util.HexDump;
-import org.apache.poi.util.LittleEndian;
-import org.apache.poi.util.LittleEndianConsts;
-import org.apache.poi.util.POILogFactory;
-import org.apache.poi.util.POILogger;
-
-public class Biff8RC4Key extends Biff8EncryptionKey {
- // these two constants coincidentally have the same value
- public static final int KEY_DIGEST_LENGTH = 5;
- private static final int PASSWORD_HASH_NUMBER_OF_BYTES_USED = 5;
-
- private static POILogger log = POILogFactory.getLogger(Biff8RC4Key.class);
-
- Biff8RC4Key(byte[] keyDigest) {
- if (keyDigest.length != KEY_DIGEST_LENGTH) {
- throw new IllegalArgumentException("Expected 5 byte key digest, but got " + HexDump.toHex(keyDigest));
- }
-
- CipherAlgorithm ca = CipherAlgorithm.rc4;
- _secretKey = new SecretKeySpec(keyDigest.clone(), ca.jceId);
- }
-
- /**
- * Create using the default password and a specified docId
- * @param salt 16 bytes
- */
- public static Biff8RC4Key create(String password, byte[] salt) {
- return new Biff8RC4Key(createKeyDigest(password, salt));
- }
-
- /**
- * @return true
if the keyDigest is compatible with the specified saltData and saltHash
- */
- public boolean validate(byte[] verifier, byte[] verifierHash) {
- check16Bytes(verifier, "verifier");
- check16Bytes(verifierHash, "verifierHash");
-
- // validation uses the RC4 for block zero
- Cipher rc4 = getCipher();
- initCipherForBlock(rc4, 0);
-
- byte[] verifierPrime = verifier.clone();
- byte[] verifierHashPrime = verifierHash.clone();
-
- try {
- rc4.update(verifierPrime, 0, verifierPrime.length, verifierPrime);
- rc4.update(verifierHashPrime, 0, verifierHashPrime.length, verifierHashPrime);
- } catch (ShortBufferException e) {
- throw new EncryptedDocumentException("buffer too short", e);
- }
-
- MessageDigest md5 = CryptoFunctions.getMessageDigest(HashAlgorithm.md5);
- md5.update(verifierPrime);
- byte[] finalVerifierResult = md5.digest();
-
- if (log.check(POILogger.DEBUG)) {
- byte[] verifierHashThatWouldWork = xor(verifierHash, xor(verifierHashPrime, finalVerifierResult));
- log.log(POILogger.DEBUG, "valid verifierHash value", HexDump.toHex(verifierHashThatWouldWork));
- }
-
- return Arrays.equals(verifierHashPrime, finalVerifierResult);
- }
-
- Cipher getCipher() {
- CipherAlgorithm ca = CipherAlgorithm.rc4;
- Cipher rc4 = CryptoFunctions.getCipher(_secretKey, ca, null, null, Cipher.ENCRYPT_MODE);
- return rc4;
- }
-
- static byte[] createKeyDigest(String password, byte[] docIdData) {
- check16Bytes(docIdData, "docId");
- int nChars = Math.min(password.length(), 16);
- byte[] passwordData = new byte[nChars*2];
- for (int i=0; i