Bug 59857 - Password protected files with "Microsoft Enhanced Cryptographic Provider v1.0"
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1762726 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
commit
d8767e1c70
@ -32,6 +32,7 @@ import org.apache.poi.hpsf.PropertySet;
|
|||||||
import org.apache.poi.hpsf.PropertySetFactory;
|
import org.apache.poi.hpsf.PropertySetFactory;
|
||||||
import org.apache.poi.hpsf.SummaryInformation;
|
import org.apache.poi.hpsf.SummaryInformation;
|
||||||
import org.apache.poi.poifs.crypt.EncryptionInfo;
|
import org.apache.poi.poifs.crypt.EncryptionInfo;
|
||||||
|
import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIDecryptor;
|
||||||
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
||||||
import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
||||||
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
|
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
|
||||||
@ -59,6 +60,8 @@ public abstract class POIDocument implements Closeable {
|
|||||||
|
|
||||||
/* Have the property streams been read yet? (Only done on-demand) */
|
/* Have the property streams been read yet? (Only done on-demand) */
|
||||||
private boolean initialized = false;
|
private boolean initialized = false;
|
||||||
|
|
||||||
|
private static final String[] encryptedStreamNames = { "EncryptedSummary" };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a POIDocument with the given directory node.
|
* Constructs a POIDocument with the given directory node.
|
||||||
@ -195,13 +198,18 @@ public abstract class POIDocument implements Closeable {
|
|||||||
try {
|
try {
|
||||||
if (encryptionInfo != null) {
|
if (encryptionInfo != null) {
|
||||||
step = "getting encrypted";
|
step = "getting encrypted";
|
||||||
InputStream is = encryptionInfo.getDecryptor().getDataStream(directory);
|
String encryptedStream = null;
|
||||||
try {
|
for (String s : encryptedStreamNames) {
|
||||||
encPoifs = new NPOIFSFileSystem(is);
|
if (dirNode.hasEntry(s)) {
|
||||||
dirNode = encPoifs.getRoot();
|
encryptedStream = s;
|
||||||
} finally {
|
}
|
||||||
is.close();
|
|
||||||
}
|
}
|
||||||
|
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
|
//directory can be null when creating new documents
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
package org.apache.poi.hssf.model;
|
package org.apache.poi.hssf.model;
|
||||||
|
|
||||||
import java.security.AccessControlException;
|
import java.security.AccessControlException;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
@ -25,6 +26,9 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
|
import org.apache.poi.EncryptedDocumentException;
|
||||||
import org.apache.poi.ddf.EscherBSERecord;
|
import org.apache.poi.ddf.EscherBSERecord;
|
||||||
import org.apache.poi.ddf.EscherBoolProperty;
|
import org.apache.poi.ddf.EscherBoolProperty;
|
||||||
import org.apache.poi.ddf.EscherContainerRecord;
|
import org.apache.poi.ddf.EscherContainerRecord;
|
||||||
@ -52,6 +56,7 @@ import org.apache.poi.hssf.record.EscherAggregate;
|
|||||||
import org.apache.poi.hssf.record.ExtSSTRecord;
|
import org.apache.poi.hssf.record.ExtSSTRecord;
|
||||||
import org.apache.poi.hssf.record.ExtendedFormatRecord;
|
import org.apache.poi.hssf.record.ExtendedFormatRecord;
|
||||||
import org.apache.poi.hssf.record.ExternSheetRecord;
|
import org.apache.poi.hssf.record.ExternSheetRecord;
|
||||||
|
import org.apache.poi.hssf.record.FilePassRecord;
|
||||||
import org.apache.poi.hssf.record.FileSharingRecord;
|
import org.apache.poi.hssf.record.FileSharingRecord;
|
||||||
import org.apache.poi.hssf.record.FnGroupCountRecord;
|
import org.apache.poi.hssf.record.FnGroupCountRecord;
|
||||||
import org.apache.poi.hssf.record.FontRecord;
|
import org.apache.poi.hssf.record.FontRecord;
|
||||||
@ -82,8 +87,13 @@ import org.apache.poi.hssf.record.WindowProtectRecord;
|
|||||||
import org.apache.poi.hssf.record.WriteAccessRecord;
|
import org.apache.poi.hssf.record.WriteAccessRecord;
|
||||||
import org.apache.poi.hssf.record.WriteProtectRecord;
|
import org.apache.poi.hssf.record.WriteProtectRecord;
|
||||||
import org.apache.poi.hssf.record.common.UnicodeString;
|
import org.apache.poi.hssf.record.common.UnicodeString;
|
||||||
|
import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
|
||||||
import org.apache.poi.hssf.util.HSSFColor;
|
import org.apache.poi.hssf.util.HSSFColor;
|
||||||
import org.apache.poi.poifs.crypt.CryptoFunctions;
|
import org.apache.poi.poifs.crypt.CryptoFunctions;
|
||||||
|
import org.apache.poi.poifs.crypt.Decryptor;
|
||||||
|
import org.apache.poi.poifs.crypt.EncryptionInfo;
|
||||||
|
import org.apache.poi.poifs.crypt.EncryptionMode;
|
||||||
|
import org.apache.poi.poifs.crypt.Encryptor;
|
||||||
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalName;
|
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalName;
|
||||||
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
|
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
|
||||||
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheetRange;
|
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheetRange;
|
||||||
@ -1082,10 +1092,8 @@ public final class InternalWorkbook {
|
|||||||
SSTRecord sst = null;
|
SSTRecord sst = null;
|
||||||
int sstPos = 0;
|
int sstPos = 0;
|
||||||
boolean wroteBoundSheets = false;
|
boolean wroteBoundSheets = false;
|
||||||
for ( int k = 0; k < records.size(); k++ )
|
for ( Record record : records ) {
|
||||||
{
|
|
||||||
|
|
||||||
Record record = records.get( k );
|
|
||||||
int len = 0;
|
int len = 0;
|
||||||
if (record instanceof SSTRecord)
|
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.
|
* Include in it ant code that modifies the workbook record stream and affects its size.
|
||||||
*/
|
*/
|
||||||
public void preSerialize(){
|
public void preSerialize(){
|
||||||
|
updateEncryptionRecord();
|
||||||
|
|
||||||
// Ensure we have enough tab IDs
|
// Ensure we have enough tab IDs
|
||||||
// Can be a few short if new sheets were added
|
// Can be a few short if new sheets were added
|
||||||
if(records.getTabpos() > 0) {
|
if(records.getTabpos() > 0) {
|
||||||
@ -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()
|
public int getSize()
|
||||||
{
|
{
|
||||||
int retval = 0;
|
int retval = 0;
|
||||||
|
@ -24,6 +24,8 @@ import java.util.List;
|
|||||||
import org.apache.poi.util.BitField;
|
import org.apache.poi.util.BitField;
|
||||||
import org.apache.poi.util.BitFieldFactory;
|
import org.apache.poi.util.BitFieldFactory;
|
||||||
import org.apache.poi.util.HexDump;
|
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.LittleEndianOutput;
|
||||||
import org.apache.poi.util.StringUtil;
|
import org.apache.poi.util.StringUtil;
|
||||||
import org.apache.poi.ss.util.WorkbookUtil;
|
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
|
* @param in the record stream to read from
|
||||||
*/
|
*/
|
||||||
public BoundSheetRecord(RecordInputStream in) {
|
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();
|
field_2_option_flags = in.readUShort();
|
||||||
int field_3_sheetname_length = in.readUByte();
|
int field_3_sheetname_length = in.readUByte();
|
||||||
field_4_isMultibyteUnicode = in.readByte();
|
field_4_isMultibyteUnicode = in.readByte();
|
||||||
|
@ -17,9 +17,22 @@
|
|||||||
|
|
||||||
package org.apache.poi.hssf.record;
|
package org.apache.poi.hssf.record;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.poi.EncryptedDocumentException;
|
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.HexDump;
|
||||||
|
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
|
||||||
import org.apache.poi.util.LittleEndianOutput;
|
import org.apache.poi.util.LittleEndianOutput;
|
||||||
|
import org.apache.poi.util.LittleEndianOutputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Title: File Pass Record (0x002F) <p>
|
* Title: File Pass Record (0x002F) <p>
|
||||||
@ -31,228 +44,92 @@ public final class FilePassRecord extends StandardRecord implements Cloneable {
|
|||||||
private static final int ENCRYPTION_XOR = 0;
|
private static final int ENCRYPTION_XOR = 0;
|
||||||
private static final int ENCRYPTION_OTHER = 1;
|
private static final int ENCRYPTION_OTHER = 1;
|
||||||
|
|
||||||
private int _encryptionType;
|
private final int encryptionType;
|
||||||
private KeyData _keyData;
|
private EncryptionInfo encryptionInfo;
|
||||||
|
|
||||||
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 FilePassRecord(FilePassRecord other) {
|
private FilePassRecord(FilePassRecord other) {
|
||||||
_encryptionType = other._encryptionType;
|
encryptionType = other.encryptionType;
|
||||||
_keyData = other._keyData.clone();
|
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) {
|
public FilePassRecord(RecordInputStream in) {
|
||||||
_encryptionType = in.readUShort();
|
encryptionType = in.readUShort();
|
||||||
|
|
||||||
switch (_encryptionType) {
|
EncryptionMode preferredMode;
|
||||||
case ENCRYPTION_XOR:
|
switch (encryptionType) {
|
||||||
_keyData = new XorKeyData();
|
case ENCRYPTION_XOR:
|
||||||
break;
|
preferredMode = EncryptionMode.xor;
|
||||||
case ENCRYPTION_OTHER:
|
break;
|
||||||
_keyData = new Rc4KeyData();
|
case ENCRYPTION_OTHER:
|
||||||
break;
|
preferredMode = EncryptionMode.cryptoAPI;
|
||||||
default:
|
break;
|
||||||
throw new RecordFormatException("Unknown encryption type " + _encryptionType);
|
default:
|
||||||
}
|
throw new EncryptedDocumentException("invalid encryption type");
|
||||||
|
|
||||||
_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.");
|
|
||||||
}
|
}
|
||||||
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;
|
return sid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,12 +138,18 @@ public final class FilePassRecord extends StandardRecord implements Cloneable {
|
|||||||
return new FilePassRecord(this);
|
return new FilePassRecord(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
@Override
|
||||||
|
public String toString() {
|
||||||
StringBuffer buffer = new StringBuffer();
|
StringBuffer buffer = new StringBuffer();
|
||||||
|
|
||||||
buffer.append("[FILEPASS]\n");
|
buffer.append("[FILEPASS]\n");
|
||||||
buffer.append(" .type = ").append(HexDump.shortToHex(_encryptionType)).append("\n");
|
buffer.append(" .type = ").append(HexDump.shortToHex(encryptionType)).append("\n");
|
||||||
_keyData.appendToString(buffer);
|
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");
|
buffer.append("[/FILEPASS]\n");
|
||||||
return buffer.toString();
|
return buffer.toString();
|
||||||
}
|
}
|
||||||
|
@ -17,18 +17,16 @@
|
|||||||
package org.apache.poi.hssf.record;
|
package org.apache.poi.hssf.record;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.poi.EncryptedDocumentException;
|
import org.apache.poi.EncryptedDocumentException;
|
||||||
import org.apache.poi.hssf.eventusermodel.HSSFEventFactory;
|
import org.apache.poi.hssf.eventusermodel.HSSFEventFactory;
|
||||||
import org.apache.poi.hssf.eventusermodel.HSSFListener;
|
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.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.Decryptor;
|
||||||
|
import org.apache.poi.poifs.crypt.EncryptionInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A stream based way to get at complete records, with
|
* A stream based way to get at complete records, with
|
||||||
@ -80,20 +78,16 @@ public final class RecordFactoryInputStream {
|
|||||||
outputRecs.add(rec);
|
outputRecs.add(rec);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's a FILEPASS, track it specifically but
|
// If it's a FILEPASS, track it specifically
|
||||||
// don't include it in the main stream
|
|
||||||
if (rec instanceof FilePassRecord) {
|
if (rec instanceof FilePassRecord) {
|
||||||
fpr = (FilePassRecord) rec;
|
fpr = (FilePassRecord) rec;
|
||||||
outputRecs.remove(outputRecs.size()-1);
|
}
|
||||||
// TODO - add fpr not added to outputRecs
|
|
||||||
rec = outputRecs.get(0);
|
// workbook not encrypted (typical case)
|
||||||
} else {
|
if (rec instanceof EOFRecord) {
|
||||||
// workbook not encrypted (typical case)
|
// A workbook stream is never empty, so crash instead
|
||||||
if (rec instanceof EOFRecord) {
|
// of trying to keep track of nesting level
|
||||||
// A workbook stream is never empty, so crash instead
|
throw new IllegalStateException("Nothing between BOF and EOF");
|
||||||
// of trying to keep track of nesting level
|
|
||||||
throw new IllegalStateException("Nothing between BOF and EOF");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -114,31 +108,18 @@ public final class RecordFactoryInputStream {
|
|||||||
userPassword = Decryptor.DEFAULT_PASSWORD;
|
userPassword = Decryptor.DEFAULT_PASSWORD;
|
||||||
}
|
}
|
||||||
|
|
||||||
Biff8EncryptionKey key;
|
EncryptionInfo info = fpr.getEncryptionInfo();
|
||||||
if (fpr.getRc4KeyData() != null) {
|
try {
|
||||||
Rc4KeyData rc4 = fpr.getRc4KeyData();
|
if (!info.getDecryptor().verifyPassword(userPassword)) {
|
||||||
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())) {
|
|
||||||
throw new EncryptedDocumentException(
|
throw new EncryptedDocumentException(
|
||||||
(Decryptor.DEFAULT_PASSWORD.equals(userPassword) ? "Default" : "Supplied")
|
(Decryptor.DEFAULT_PASSWORD.equals(userPassword) ? "Default" : "Supplied")
|
||||||
+ " password is invalid for key/verifier");
|
+ " password is invalid for salt/verifier/verifierHash");
|
||||||
}
|
}
|
||||||
} else {
|
} catch (GeneralSecurityException e) {
|
||||||
throw new EncryptedDocumentException("Crypto API not yet supported.");
|
throw new EncryptedDocumentException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new RecordInputStream(original, key, _initialRecordsSize);
|
return new RecordInputStream(original, info, _initialRecordsSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasEncryption() {
|
public boolean hasEncryption() {
|
||||||
|
@ -18,13 +18,14 @@
|
|||||||
package org.apache.poi.hssf.record;
|
package org.apache.poi.hssf.record;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.apache.poi.hssf.dev.BiffViewer;
|
import org.apache.poi.hssf.dev.BiffViewer;
|
||||||
import org.apache.poi.hssf.record.crypto.Biff8DecryptingStream;
|
import org.apache.poi.hssf.record.crypto.Biff8DecryptingStream;
|
||||||
import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
|
import org.apache.poi.poifs.crypt.EncryptionInfo;
|
||||||
import org.apache.poi.util.LittleEndian;
|
import org.apache.poi.util.Internal;
|
||||||
import org.apache.poi.util.LittleEndianConsts;
|
import org.apache.poi.util.LittleEndianConsts;
|
||||||
import org.apache.poi.util.LittleEndianInput;
|
import org.apache.poi.util.LittleEndianInput;
|
||||||
import org.apache.poi.util.LittleEndianInputStream;
|
import org.apache.poi.util.LittleEndianInputStream;
|
||||||
@ -32,8 +33,6 @@ import org.apache.poi.util.LittleEndianInputStream;
|
|||||||
/**
|
/**
|
||||||
* Title: Record Input Stream<P>
|
* Title: Record Input Stream<P>
|
||||||
* Description: Wraps a stream and provides helper methods for the construction of records.<P>
|
* Description: Wraps a stream and provides helper methods for the construction of records.<P>
|
||||||
*
|
|
||||||
* @author Jason Height (jheight @ apache dot org)
|
|
||||||
*/
|
*/
|
||||||
public final class RecordInputStream implements LittleEndianInput {
|
public final class RecordInputStream implements LittleEndianInput {
|
||||||
/** Maximum size of a single record (minus the 4 byte header) without a continue*/
|
/** 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
|
* index within the data section of the current BIFF record
|
||||||
*/
|
*/
|
||||||
private int _currentDataOffset;
|
private int _currentDataOffset;
|
||||||
|
/**
|
||||||
|
* index within the data section when mark() was called
|
||||||
|
*/
|
||||||
|
private int _markedDataOffset;
|
||||||
|
|
||||||
private static final class SimpleHeaderInput implements BiffHeaderInput {
|
private static final class SimpleHeaderInput implements BiffHeaderInput {
|
||||||
|
|
||||||
@ -117,14 +120,14 @@ public final class RecordInputStream implements LittleEndianInput {
|
|||||||
this (in, null, 0);
|
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) {
|
if (key == null) {
|
||||||
_dataInput = getLEI(in);
|
_dataInput = getLEI(in);
|
||||||
_bhi = new SimpleHeaderInput(in);
|
_bhi = new SimpleHeaderInput(in);
|
||||||
} else {
|
} else {
|
||||||
Biff8DecryptingStream bds = new Biff8DecryptingStream(in, initialOffset, key);
|
Biff8DecryptingStream bds = new Biff8DecryptingStream(in, initialOffset, key);
|
||||||
|
_dataInput = bds;
|
||||||
_bhi = bds;
|
_bhi = bds;
|
||||||
_dataInput = bds;
|
|
||||||
}
|
}
|
||||||
_nextSid = readNextSid();
|
_nextSid = readNextSid();
|
||||||
}
|
}
|
||||||
@ -305,13 +308,22 @@ public final class RecordInputStream implements LittleEndianInput {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void readPlain(byte[] buf, int off, int len) {
|
||||||
|
readFully(buf, 0, buf.length, true);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readFully(byte[] buf) {
|
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) {
|
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;
|
int origLen = len;
|
||||||
if (buf == null) {
|
if (buf == null) {
|
||||||
throw new NullPointerException();
|
throw new NullPointerException();
|
||||||
@ -331,7 +343,11 @@ public final class RecordInputStream implements LittleEndianInput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
checkRecordPosition(nextChunk);
|
checkRecordPosition(nextChunk);
|
||||||
_dataInput.readFully(buf, off, nextChunk);
|
if (isPlain) {
|
||||||
|
_dataInput.readPlain(buf, off, nextChunk);
|
||||||
|
} else {
|
||||||
|
_dataInput.readFully(buf, off, nextChunk);
|
||||||
|
}
|
||||||
_currentDataOffset+=nextChunk;
|
_currentDataOffset+=nextChunk;
|
||||||
off += nextChunk;
|
off += nextChunk;
|
||||||
len -= nextChunk;
|
len -= nextChunk;
|
||||||
@ -491,4 +507,31 @@ public final class RecordInputStream implements LittleEndianInput {
|
|||||||
public int getNextSid() {
|
public int getNextSid() {
|
||||||
return _nextSid;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,28 +54,34 @@ public class ContinuableRecordInput implements LittleEndianInput {
|
|||||||
public ContinuableRecordInput(RecordInputStream in){
|
public ContinuableRecordInput(RecordInputStream in){
|
||||||
_in = in;
|
_in = in;
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
public int available(){
|
public int available(){
|
||||||
return _in.available();
|
return _in.available();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public byte readByte(){
|
public byte readByte(){
|
||||||
return _in.readByte();
|
return _in.readByte();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int readUByte(){
|
public int readUByte(){
|
||||||
return _in.readUByte();
|
return _in.readUByte();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public short readShort(){
|
public short readShort(){
|
||||||
return _in.readShort();
|
return _in.readShort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int readUShort(){
|
public int readUShort(){
|
||||||
int ch1 = readUByte();
|
int ch1 = readUByte();
|
||||||
int ch2 = readUByte();
|
int ch2 = readUByte();
|
||||||
return (ch2 << 8) + (ch1 << 0);
|
return (ch2 << 8) + (ch1 << 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int readInt(){
|
public int readInt(){
|
||||||
int ch1 = _in.readUByte();
|
int ch1 = _in.readUByte();
|
||||||
int ch2 = _in.readUByte();
|
int ch2 = _in.readUByte();
|
||||||
@ -84,6 +90,7 @@ public class ContinuableRecordInput implements LittleEndianInput {
|
|||||||
return (ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0);
|
return (ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public long readLong(){
|
public long readLong(){
|
||||||
int b0 = _in.readUByte();
|
int b0 = _in.readUByte();
|
||||||
int b1 = _in.readUByte();
|
int b1 = _in.readUByte();
|
||||||
@ -103,14 +110,23 @@ public class ContinuableRecordInput implements LittleEndianInput {
|
|||||||
(b0 << 0));
|
(b0 << 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public double readDouble(){
|
public double readDouble(){
|
||||||
return _in.readDouble();
|
return _in.readDouble();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void readFully(byte[] buf){
|
public void readFully(byte[] buf){
|
||||||
_in.readFully(buf);
|
_in.readFully(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void readFully(byte[] buf, int off, int len){
|
public void readFully(byte[] buf, int off, int len){
|
||||||
_in.readFully(buf, off, len);
|
_in.readFully(buf, off, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readPlain(byte[] buf, int off, int len) {
|
||||||
|
readFully(buf, off, len);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,102 +18,194 @@
|
|||||||
package org.apache.poi.hssf.record.crypto;
|
package org.apache.poi.hssf.record.crypto;
|
||||||
|
|
||||||
import java.io.InputStream;
|
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.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.LittleEndianInput;
|
||||||
import org.apache.poi.util.LittleEndianInputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Josh Micich
|
|
||||||
*/
|
|
||||||
public final class Biff8DecryptingStream implements BiffHeaderInput, LittleEndianInput {
|
public final class Biff8DecryptingStream implements BiffHeaderInput, LittleEndianInput {
|
||||||
|
|
||||||
private final LittleEndianInput _le;
|
public static final int RC4_REKEYING_INTERVAL = 1024;
|
||||||
private final Biff8Cipher _cipher;
|
|
||||||
|
|
||||||
public Biff8DecryptingStream(InputStream in, int initialOffset, Biff8EncryptionKey key) {
|
private final EncryptionInfo info;
|
||||||
if (key instanceof Biff8RC4Key) {
|
private ChunkedCipherInputStream ccis;
|
||||||
_cipher = new Biff8RC4(initialOffset, (Biff8RC4Key)key);
|
private final byte buffer[] = new byte[LittleEndianConsts.LONG_SIZE];
|
||||||
} else if (key instanceof Biff8XORKey) {
|
private boolean shouldSkipEncryptionOnCurrentRecord = false;
|
||||||
_cipher = new Biff8XOR(initialOffset, (Biff8XORKey)key);
|
|
||||||
} else {
|
|
||||||
throw new EncryptedDocumentException("Crypto API not supported yet.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (in instanceof LittleEndianInput) {
|
public Biff8DecryptingStream(InputStream in, int initialOffset, EncryptionInfo info) throws RecordFormatException {
|
||||||
// accessing directly is an optimisation
|
try {
|
||||||
_le = (LittleEndianInput) in;
|
byte initialBuf[] = new byte[initialOffset];
|
||||||
} else {
|
InputStream stream;
|
||||||
// less optimal, but should work OK just the same. Often occurs in junit tests.
|
if (initialOffset == 0) {
|
||||||
_le = new LittleEndianInputStream(in);
|
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() {
|
@Override
|
||||||
return _le.available();
|
public int available() {
|
||||||
|
return ccis.available();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads an unsigned short value without decrypting
|
* Reads an unsigned short value without decrypting
|
||||||
*/
|
*/
|
||||||
public int readRecordSID() {
|
@Override
|
||||||
int sid = _le.readUShort();
|
public int readRecordSID() {
|
||||||
_cipher.skipTwoBytes();
|
readPlain(buffer, 0, LittleEndianConsts.SHORT_SIZE);
|
||||||
_cipher.startRecord(sid);
|
int sid = LittleEndian.getUShort(buffer, 0);
|
||||||
|
shouldSkipEncryptionOnCurrentRecord = isNeverEncryptedRecord(sid);
|
||||||
return sid;
|
return sid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads an unsigned short value without decrypting
|
* Reads an unsigned short value without decrypting
|
||||||
*/
|
*/
|
||||||
public int readDataSize() {
|
@Override
|
||||||
int dataSize = _le.readUShort();
|
public int readDataSize() {
|
||||||
_cipher.skipTwoBytes();
|
readPlain(buffer, 0, LittleEndianConsts.SHORT_SIZE);
|
||||||
_cipher.setNextRecordSize(dataSize);
|
int dataSize = LittleEndian.getUShort(buffer, 0);
|
||||||
|
ccis.setNextRecordSize(dataSize);
|
||||||
return dataSize;
|
return dataSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double readDouble() {
|
@Override
|
||||||
long valueLongBits = readLong();
|
public double readDouble() {
|
||||||
|
long valueLongBits = readLong();
|
||||||
double result = Double.longBitsToDouble(valueLongBits);
|
double result = Double.longBitsToDouble(valueLongBits);
|
||||||
if (Double.isNaN(result)) {
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void readFully(byte[] buf) {
|
@Override
|
||||||
readFully(buf, 0, buf.length);
|
public void readFully(byte[] buf) {
|
||||||
|
readFully(buf, 0, buf.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void readFully(byte[] buf, int off, int len) {
|
@Override
|
||||||
_le.readFully(buf, off, len);
|
public void readFully(byte[] buf, int off, int len) {
|
||||||
_cipher.xor(buf, off, len);
|
if (shouldSkipEncryptionOnCurrentRecord) {
|
||||||
|
readPlain(buf, off, buf.length);
|
||||||
|
} else {
|
||||||
|
ccis.readFully(buf, off, len);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int readUByte() {
|
public int readUByte() {
|
||||||
return readByte() & 0xFF;
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int readUShort() {
|
public int readUShort() {
|
||||||
return readShort() & 0xFFFF;
|
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() {
|
@Override
|
||||||
return _cipher.xorInt(_le.readInt());
|
public int readInt() {
|
||||||
|
if (shouldSkipEncryptionOnCurrentRecord) {
|
||||||
|
readPlain(buffer, 0, LittleEndianConsts.INT_SIZE);
|
||||||
|
return LittleEndian.getInt(buffer);
|
||||||
|
} else {
|
||||||
|
return ccis.readInt();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public long readLong() {
|
@Override
|
||||||
return _cipher.xorLong(_le.readLong());
|
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 <code>true</code> if record type specified by <tt>sid</tt> is never encrypted
|
||||||
|
*/
|
||||||
|
public static boolean isNeverEncryptedRecord(int sid) {
|
||||||
|
switch (sid) {
|
||||||
|
case BOFRecord.sid:
|
||||||
|
// sheet BOFs for sure
|
||||||
|
// TODO - find out about chart BOFs
|
||||||
|
|
||||||
|
case InterfaceHdrRecord.sid:
|
||||||
|
// don't know why this record doesn't seem to get encrypted
|
||||||
|
|
||||||
|
case FilePassRecord.sid:
|
||||||
|
// this only really counts when writing because FILEPASS is read early
|
||||||
|
|
||||||
|
// UsrExcl(0x0194)
|
||||||
|
// FileLock
|
||||||
|
// RRDInfo(0x0196)
|
||||||
|
// RRDHead(0x0138)
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readPlain(byte b[], int off, int len) {
|
||||||
|
ccis.readPlain(b, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,34 +16,9 @@
|
|||||||
==================================================================== */
|
==================================================================== */
|
||||||
package org.apache.poi.hssf.record.crypto;
|
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.hssf.usermodel.HSSFWorkbook;
|
||||||
import org.apache.poi.poifs.crypt.Decryptor;
|
|
||||||
|
|
||||||
public abstract class Biff8EncryptionKey {
|
|
||||||
protected SecretKey _secretKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create using the default password and a specified docId
|
|
||||||
* @param salt 16 bytes
|
|
||||||
*/
|
|
||||||
public static Biff8EncryptionKey create(byte[] salt) {
|
|
||||||
return Biff8RC4Key.create(Decryptor.DEFAULT_PASSWORD, salt);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Biff8EncryptionKey create(String password, byte[] salt) {
|
|
||||||
return Biff8RC4Key.create(password, salt);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return <code>true</code> if the keyDigest is compatible with the specified saltData and saltHash
|
|
||||||
*/
|
|
||||||
public boolean validate(byte[] saltData, byte[] saltHash) {
|
|
||||||
throw new EncryptedDocumentException("validate is not supported (in super-class).");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public final class Biff8EncryptionKey {
|
||||||
/**
|
/**
|
||||||
* Stores the BIFF8 encryption/decryption password for the current thread. This has been done
|
* 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
|
* using a {@link ThreadLocal} in order to avoid further overloading the various public APIs
|
||||||
|
@ -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 <code>true</code> if record type specified by <tt>sid</tt> 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 */
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 <code>true</code> 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<nChars; i++) {
|
|
||||||
char ch = password.charAt(i);
|
|
||||||
passwordData[i*2+0] = (byte) ((ch << 0) & 0xFF);
|
|
||||||
passwordData[i*2+1] = (byte) ((ch << 8) & 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageDigest md5 = CryptoFunctions.getMessageDigest(HashAlgorithm.md5);
|
|
||||||
md5.update(passwordData);
|
|
||||||
byte[] passwordHash = md5.digest();
|
|
||||||
md5.reset();
|
|
||||||
|
|
||||||
for (int i=0; i<16; i++) {
|
|
||||||
md5.update(passwordHash, 0, PASSWORD_HASH_NUMBER_OF_BYTES_USED);
|
|
||||||
md5.update(docIdData, 0, docIdData.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] result = CryptoFunctions.getBlock0(md5.digest(), KEY_DIGEST_LENGTH);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void initCipherForBlock(Cipher rc4, int keyBlockNo) {
|
|
||||||
byte buf[] = new byte[LittleEndianConsts.INT_SIZE];
|
|
||||||
LittleEndian.putInt(buf, 0, keyBlockNo);
|
|
||||||
|
|
||||||
MessageDigest md5 = CryptoFunctions.getMessageDigest(HashAlgorithm.md5);
|
|
||||||
md5.update(_secretKey.getEncoded());
|
|
||||||
md5.update(buf);
|
|
||||||
|
|
||||||
SecretKeySpec skeySpec = new SecretKeySpec(md5.digest(), _secretKey.getAlgorithm());
|
|
||||||
try {
|
|
||||||
rc4.init(Cipher.ENCRYPT_MODE, skeySpec);
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
throw new EncryptedDocumentException("Can't rekey for next block", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] xor(byte[] a, byte[] b) {
|
|
||||||
byte[] c = new byte[a.length];
|
|
||||||
for (int i = 0; i < c.length; i++) {
|
|
||||||
c[i] = (byte) (a[i] ^ b[i]);
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
private static void check16Bytes(byte[] data, String argName) {
|
|
||||||
if (data.length != 16) {
|
|
||||||
throw new IllegalArgumentException("Expected 16 byte " + argName + ", but got " + HexDump.toHex(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,153 +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 org.apache.poi.hssf.record.BOFRecord;
|
|
||||||
import org.apache.poi.hssf.record.FilePassRecord;
|
|
||||||
import org.apache.poi.hssf.record.InterfaceHdrRecord;
|
|
||||||
|
|
||||||
public class Biff8XOR implements Biff8Cipher {
|
|
||||||
|
|
||||||
private final Biff8XORKey _key;
|
|
||||||
private ByteBuffer _buffer = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN);
|
|
||||||
private boolean _shouldSkipEncryptionOnCurrentRecord;
|
|
||||||
private final int _initialOffset;
|
|
||||||
private int _dataLength = 0;
|
|
||||||
private int _xorArrayIndex = 0;
|
|
||||||
|
|
||||||
public Biff8XOR(int initialOffset, Biff8XORKey key) {
|
|
||||||
_key = key;
|
|
||||||
_initialOffset = initialOffset;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startRecord(int currentSid) {
|
|
||||||
_shouldSkipEncryptionOnCurrentRecord = isNeverEncryptedRecord(currentSid);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNextRecordSize(int recordSize) {
|
|
||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
_xorArrayIndex = (_initialOffset+_dataLength+recordSize) % 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: Additionally, the lbPlyPos (position_of_BOF) field of the BoundSheet8 record MUST NOT be encrypted.
|
|
||||||
*
|
|
||||||
* @return <code>true</code> if record type specified by <tt>sid</tt> is never encrypted
|
|
||||||
*/
|
|
||||||
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() {
|
|
||||||
_dataLength += 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrypts a xor obfuscated byte array.
|
|
||||||
* The data is decrypted in-place
|
|
||||||
*
|
|
||||||
* @see <a href="http://msdn.microsoft.com/en-us/library/dd908506.aspx">2.3.7.3 Binary Document XOR Data Transformation Method 1</a>
|
|
||||||
*/
|
|
||||||
public void xor(byte[] buf, int pOffset, int pLen) {
|
|
||||||
if (_shouldSkipEncryptionOnCurrentRecord) {
|
|
||||||
_dataLength += pLen;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 xorArray[] = _key._secretKey.getEncoded();
|
|
||||||
|
|
||||||
for (int i=0; i<pLen; i++) {
|
|
||||||
byte value = buf[pOffset+i];
|
|
||||||
value = rotateLeft(value, 3);
|
|
||||||
value ^= xorArray[_xorArrayIndex];
|
|
||||||
buf[pOffset+i] = value;
|
|
||||||
_xorArrayIndex = (_xorArrayIndex + 1) % 16;
|
|
||||||
_dataLength++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte rotateLeft(byte bits, int shift) {
|
|
||||||
return (byte)(((bits & 0xff) << shift) | ((bits & 0xff) >>> (8 - shift)));
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +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 javax.crypto.spec.SecretKeySpec;
|
|
||||||
|
|
||||||
import org.apache.poi.poifs.crypt.CryptoFunctions;
|
|
||||||
|
|
||||||
|
|
||||||
public class Biff8XORKey extends Biff8EncryptionKey {
|
|
||||||
final int _xorKey;
|
|
||||||
|
|
||||||
public Biff8XORKey(String password, int xorKey) {
|
|
||||||
_xorKey = xorKey;
|
|
||||||
byte xorArray[] = CryptoFunctions.createXorArray1(password);
|
|
||||||
_secretKey = new SecretKeySpec(xorArray, "XOR");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Biff8XORKey create(String password, int xorKey) {
|
|
||||||
return new Biff8XORKey(password, xorKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean validate(String password, int verifier) {
|
|
||||||
int keyComp = CryptoFunctions.createXorKey1(password);
|
|
||||||
int verifierComp = CryptoFunctions.createXorVerifier1(password);
|
|
||||||
|
|
||||||
return (_xorKey == keyComp && verifierComp == verifier);
|
|
||||||
}
|
|
||||||
}
|
|
@ -61,8 +61,10 @@ import org.apache.poi.hssf.model.InternalWorkbook;
|
|||||||
import org.apache.poi.hssf.model.RecordStream;
|
import org.apache.poi.hssf.model.RecordStream;
|
||||||
import org.apache.poi.hssf.record.AbstractEscherHolderRecord;
|
import org.apache.poi.hssf.record.AbstractEscherHolderRecord;
|
||||||
import org.apache.poi.hssf.record.BackupRecord;
|
import org.apache.poi.hssf.record.BackupRecord;
|
||||||
|
import org.apache.poi.hssf.record.BoundSheetRecord;
|
||||||
import org.apache.poi.hssf.record.DrawingGroupRecord;
|
import org.apache.poi.hssf.record.DrawingGroupRecord;
|
||||||
import org.apache.poi.hssf.record.ExtendedFormatRecord;
|
import org.apache.poi.hssf.record.ExtendedFormatRecord;
|
||||||
|
import org.apache.poi.hssf.record.FilePassRecord;
|
||||||
import org.apache.poi.hssf.record.FontRecord;
|
import org.apache.poi.hssf.record.FontRecord;
|
||||||
import org.apache.poi.hssf.record.LabelRecord;
|
import org.apache.poi.hssf.record.LabelRecord;
|
||||||
import org.apache.poi.hssf.record.LabelSSTRecord;
|
import org.apache.poi.hssf.record.LabelSSTRecord;
|
||||||
@ -74,8 +76,11 @@ import org.apache.poi.hssf.record.SSTRecord;
|
|||||||
import org.apache.poi.hssf.record.UnknownRecord;
|
import org.apache.poi.hssf.record.UnknownRecord;
|
||||||
import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor;
|
import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor;
|
||||||
import org.apache.poi.hssf.record.common.UnicodeString;
|
import org.apache.poi.hssf.record.common.UnicodeString;
|
||||||
|
import org.apache.poi.hssf.record.crypto.Biff8DecryptingStream;
|
||||||
import org.apache.poi.hssf.util.CellReference;
|
import org.apache.poi.hssf.util.CellReference;
|
||||||
|
import org.apache.poi.poifs.crypt.ChunkedCipherOutputStream;
|
||||||
import org.apache.poi.poifs.crypt.Decryptor;
|
import org.apache.poi.poifs.crypt.Decryptor;
|
||||||
|
import org.apache.poi.poifs.crypt.Encryptor;
|
||||||
import org.apache.poi.poifs.filesystem.DirectoryEntry;
|
import org.apache.poi.poifs.filesystem.DirectoryEntry;
|
||||||
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
||||||
import org.apache.poi.poifs.filesystem.DocumentNode;
|
import org.apache.poi.poifs.filesystem.DocumentNode;
|
||||||
@ -101,6 +106,8 @@ import org.apache.poi.util.Configurator;
|
|||||||
import org.apache.poi.util.HexDump;
|
import org.apache.poi.util.HexDump;
|
||||||
import org.apache.poi.util.Internal;
|
import org.apache.poi.util.Internal;
|
||||||
import org.apache.poi.util.LittleEndian;
|
import org.apache.poi.util.LittleEndian;
|
||||||
|
import org.apache.poi.util.LittleEndianByteArrayInputStream;
|
||||||
|
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
|
||||||
import org.apache.poi.util.POILogFactory;
|
import org.apache.poi.util.POILogFactory;
|
||||||
import org.apache.poi.util.POILogger;
|
import org.apache.poi.util.POILogger;
|
||||||
|
|
||||||
@ -119,7 +126,6 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
|
|||||||
* The maximum number of cell styles in a .xls workbook.
|
* The maximum number of cell styles in a .xls workbook.
|
||||||
* The 'official' limit is 4,000, but POI allows a slightly larger number.
|
* The 'official' limit is 4,000, but POI allows a slightly larger number.
|
||||||
* This extra delta takes into account built-in styles that are automatically
|
* This extra delta takes into account built-in styles that are automatically
|
||||||
* created for new workbooks
|
|
||||||
*
|
*
|
||||||
* See http://office.microsoft.com/en-us/excel-help/excel-specifications-and-limits-HP005199291.aspx
|
* See http://office.microsoft.com/en-us/excel-help/excel-specifications-and-limits-HP005199291.aspx
|
||||||
*/
|
*/
|
||||||
@ -1444,7 +1450,7 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
|
|||||||
if (log.check( POILogger.DEBUG )) {
|
if (log.check( POILogger.DEBUG )) {
|
||||||
log.log(DEBUG, "HSSFWorkbook.getBytes()");
|
log.log(DEBUG, "HSSFWorkbook.getBytes()");
|
||||||
}
|
}
|
||||||
|
|
||||||
HSSFSheet[] sheets = getSheets();
|
HSSFSheet[] sheets = getSheets();
|
||||||
int nSheets = sheets.length;
|
int nSheets = sheets.length;
|
||||||
|
|
||||||
@ -1486,9 +1492,70 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
|
|||||||
}
|
}
|
||||||
pos += serializedSize;
|
pos += serializedSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
encryptBytes(retval);
|
||||||
|
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("resource")
|
||||||
|
protected void encryptBytes(byte buf[]) {
|
||||||
|
int initialOffset = 0;
|
||||||
|
FilePassRecord fpr = null;
|
||||||
|
for (Record r : workbook.getRecords()) {
|
||||||
|
initialOffset += r.getRecordSize();
|
||||||
|
if (r instanceof FilePassRecord) {
|
||||||
|
fpr = (FilePassRecord)r;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fpr == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LittleEndianByteArrayInputStream plain = new LittleEndianByteArrayInputStream(buf, 0);
|
||||||
|
LittleEndianByteArrayOutputStream leos = new LittleEndianByteArrayOutputStream(buf, 0);
|
||||||
|
Encryptor enc = fpr.getEncryptionInfo().getEncryptor();
|
||||||
|
enc.setChunkSize(Biff8DecryptingStream.RC4_REKEYING_INTERVAL);
|
||||||
|
byte tmp[] = new byte[1024];
|
||||||
|
try {
|
||||||
|
ChunkedCipherOutputStream os = enc.getDataStream(leos, initialOffset);
|
||||||
|
int totalBytes = 0;
|
||||||
|
while (totalBytes < buf.length) {
|
||||||
|
plain.read(tmp, 0, 4);
|
||||||
|
final int sid = LittleEndian.getUShort(tmp, 0);
|
||||||
|
final int len = LittleEndian.getUShort(tmp, 2);
|
||||||
|
boolean isPlain = Biff8DecryptingStream.isNeverEncryptedRecord(sid);
|
||||||
|
os.setNextRecordSize(len, isPlain);
|
||||||
|
os.writePlain(tmp, 0, 4);
|
||||||
|
if (sid == BoundSheetRecord.sid) {
|
||||||
|
// special case for the field_1_position_of_BOF (=lbPlyPos) field of
|
||||||
|
// the BoundSheet8 record which must be unencrypted
|
||||||
|
byte bsrBuf[] = new byte[len];
|
||||||
|
plain.readFully(bsrBuf);
|
||||||
|
os.writePlain(bsrBuf, 0, 4);
|
||||||
|
os.write(bsrBuf, 4, len-4);
|
||||||
|
} else {
|
||||||
|
int todo = len;
|
||||||
|
while (todo > 0) {
|
||||||
|
int nextLen = Math.min(todo, tmp.length);
|
||||||
|
plain.readFully(tmp, 0, nextLen);
|
||||||
|
if (isPlain) {
|
||||||
|
os.writePlain(tmp, 0, nextLen);
|
||||||
|
} else {
|
||||||
|
os.write(tmp, 0, nextLen);
|
||||||
|
}
|
||||||
|
todo -= nextLen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
totalBytes += 4 + len;
|
||||||
|
}
|
||||||
|
os.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new EncryptedDocumentException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*package*/ InternalWorkbook getWorkbook() {
|
/*package*/ InternalWorkbook getWorkbook() {
|
||||||
return workbook;
|
return workbook;
|
||||||
}
|
}
|
||||||
|
@ -16,78 +16,113 @@
|
|||||||
==================================================================== */
|
==================================================================== */
|
||||||
package org.apache.poi.poifs.crypt;
|
package org.apache.poi.poifs.crypt;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
|
import javax.crypto.BadPaddingException;
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
import javax.crypto.ShortBufferException;
|
||||||
|
|
||||||
import org.apache.poi.EncryptedDocumentException;
|
import org.apache.poi.EncryptedDocumentException;
|
||||||
import org.apache.poi.util.Internal;
|
import org.apache.poi.util.Internal;
|
||||||
import org.apache.poi.util.LittleEndianInput;
|
|
||||||
import org.apache.poi.util.LittleEndianInputStream;
|
import org.apache.poi.util.LittleEndianInputStream;
|
||||||
|
|
||||||
@Internal
|
@Internal
|
||||||
public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
|
public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
|
||||||
private final int chunkSize;
|
private final int _chunkSize;
|
||||||
private final int chunkMask;
|
private final int _chunkBits;
|
||||||
private final int chunkBits;
|
|
||||||
|
|
||||||
private int _lastIndex = 0;
|
|
||||||
private long _pos = 0;
|
|
||||||
private long _size;
|
|
||||||
private byte[] _chunk;
|
|
||||||
private Cipher _cipher;
|
|
||||||
|
|
||||||
public ChunkedCipherInputStream(LittleEndianInput stream, long size, int chunkSize)
|
private final long _size;
|
||||||
throws GeneralSecurityException {
|
private final byte[] _chunk, _plain;
|
||||||
super((InputStream)stream);
|
private final Cipher _cipher;
|
||||||
_size = size;
|
|
||||||
this.chunkSize = chunkSize;
|
private int _lastIndex;
|
||||||
chunkMask = chunkSize-1;
|
private long _pos;
|
||||||
chunkBits = Integer.bitCount(chunkMask);
|
private boolean _chunkIsValid = false;
|
||||||
|
|
||||||
_cipher = initCipherForBlock(null, 0);
|
public ChunkedCipherInputStream(InputStream stream, long size, int chunkSize)
|
||||||
|
throws GeneralSecurityException {
|
||||||
|
this(stream, size, chunkSize, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ChunkedCipherInputStream(InputStream stream, long size, int chunkSize, int initialPos)
|
||||||
|
throws GeneralSecurityException {
|
||||||
|
super(stream);
|
||||||
|
_size = size;
|
||||||
|
_pos = initialPos;
|
||||||
|
this._chunkSize = chunkSize;
|
||||||
|
int cs = chunkSize == -1 ? 4096 : chunkSize;
|
||||||
|
_chunk = new byte[cs];
|
||||||
|
_plain = new byte[cs];
|
||||||
|
_chunkBits = Integer.bitCount(_chunk.length-1);
|
||||||
|
_lastIndex = (int)(_pos >> _chunkBits);
|
||||||
|
_cipher = initCipherForBlock(null, _lastIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Cipher initCipherForBlock(int block) throws IOException, GeneralSecurityException {
|
||||||
|
if (_chunkSize != -1) {
|
||||||
|
throw new GeneralSecurityException("the cipher block can only be set for streaming encryption, e.g. CryptoAPI...");
|
||||||
|
}
|
||||||
|
|
||||||
|
_chunkIsValid = false;
|
||||||
|
return initCipherForBlock(_cipher, block);
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract Cipher initCipherForBlock(Cipher existing, int block)
|
protected abstract Cipher initCipherForBlock(Cipher existing, int block)
|
||||||
throws GeneralSecurityException;
|
throws GeneralSecurityException;
|
||||||
|
|
||||||
|
@Override
|
||||||
public int read() throws IOException {
|
public int read() throws IOException {
|
||||||
byte[] b = new byte[1];
|
byte[] b = new byte[1];
|
||||||
if (read(b) == 1)
|
if (read(b) == 1) {
|
||||||
return b[0];
|
return b[0];
|
||||||
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not implement! -> recursion
|
// do not implement! -> recursion
|
||||||
// public int read(byte[] b) throws IOException;
|
// public int read(byte[] b) throws IOException;
|
||||||
|
|
||||||
|
@Override
|
||||||
public int read(byte[] b, int off, int len) throws IOException {
|
public int read(byte[] b, int off, int len) throws IOException {
|
||||||
int total = 0;
|
return read(b, off, len, false);
|
||||||
|
}
|
||||||
if (available() <= 0) return -1;
|
|
||||||
|
|
||||||
|
private int read(byte[] b, int off, int len, boolean readPlain) throws IOException {
|
||||||
|
int total = 0;
|
||||||
|
|
||||||
|
if (available() <= 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int chunkMask = getChunkMask();
|
||||||
while (len > 0) {
|
while (len > 0) {
|
||||||
if (_chunk == null) {
|
if (!_chunkIsValid) {
|
||||||
try {
|
try {
|
||||||
_chunk = nextChunk();
|
nextChunk();
|
||||||
|
_chunkIsValid = true;
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
throw new EncryptedDocumentException(e.getMessage(), e);
|
throw new EncryptedDocumentException(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int count = (int)(chunkSize - (_pos & chunkMask));
|
int count = (int)(_chunk.length - (_pos & chunkMask));
|
||||||
int avail = available();
|
int avail = available();
|
||||||
if (avail == 0) {
|
if (avail == 0) {
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
count = Math.min(avail, Math.min(count, len));
|
count = Math.min(avail, Math.min(count, len));
|
||||||
System.arraycopy(_chunk, (int)(_pos & chunkMask), b, off, count);
|
|
||||||
|
System.arraycopy(readPlain ? _plain : _chunk, (int)(_pos & chunkMask), b, off, count);
|
||||||
|
|
||||||
off += count;
|
off += count;
|
||||||
len -= count;
|
len -= count;
|
||||||
_pos += count;
|
_pos += count;
|
||||||
if ((_pos & chunkMask) == 0)
|
if ((_pos & chunkMask) == 0) {
|
||||||
_chunk = null;
|
_chunkIsValid = false;
|
||||||
|
}
|
||||||
total += count;
|
total += count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,21 +130,31 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long skip(long n) throws IOException {
|
public long skip(final long n) throws IOException {
|
||||||
long start = _pos;
|
long start = _pos;
|
||||||
long skip = Math.min(available(), n);
|
long skip = Math.min(remainingBytes(), n);
|
||||||
|
|
||||||
if ((((_pos + skip) ^ start) & ~chunkMask) != 0)
|
if ((((_pos + skip) ^ start) & ~getChunkMask()) != 0) {
|
||||||
_chunk = null;
|
_chunkIsValid = false;
|
||||||
|
}
|
||||||
_pos += skip;
|
_pos += skip;
|
||||||
return skip;
|
return skip;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int available() {
|
public int available() {
|
||||||
|
return remainingBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method for forbidden available call - we know the size beforehand, so it's ok ...
|
||||||
|
*
|
||||||
|
* @return the remaining byte until EOF
|
||||||
|
*/
|
||||||
|
private int remainingBytes() {
|
||||||
return (int)(_size - _pos);
|
return (int)(_size - _pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean markSupported() {
|
public boolean markSupported() {
|
||||||
return false;
|
return false;
|
||||||
@ -119,23 +164,115 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
|
|||||||
public synchronized void mark(int readlimit) {
|
public synchronized void mark(int readlimit) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void reset() throws IOException {
|
public synchronized void reset() throws IOException {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] nextChunk() throws GeneralSecurityException, IOException {
|
protected int getChunkMask() {
|
||||||
int index = (int)(_pos >> chunkBits);
|
return _chunk.length-1;
|
||||||
initCipherForBlock(_cipher, index);
|
}
|
||||||
|
|
||||||
if (_lastIndex != index) {
|
private void nextChunk() throws GeneralSecurityException, IOException {
|
||||||
super.skip((index - _lastIndex) << chunkBits);
|
if (_chunkSize != -1) {
|
||||||
|
int index = (int)(_pos >> _chunkBits);
|
||||||
|
initCipherForBlock(_cipher, index);
|
||||||
|
|
||||||
|
if (_lastIndex != index) {
|
||||||
|
super.skip((index - _lastIndex) << _chunkBits);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastIndex = index + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] block = new byte[Math.min(super.available(), chunkSize)];
|
final int todo = (int)Math.min(_size, _chunk.length);
|
||||||
super.read(block, 0, block.length);
|
int readBytes = 0, totalBytes = 0;
|
||||||
_lastIndex = index + 1;
|
do {
|
||||||
return _cipher.doFinal(block);
|
readBytes = super.read(_plain, totalBytes, todo-totalBytes);
|
||||||
|
totalBytes += Math.max(0, readBytes);
|
||||||
|
} while (readBytes != -1 && totalBytes < todo);
|
||||||
|
|
||||||
|
if (readBytes == -1 && _pos+totalBytes < _size && _size < Integer.MAX_VALUE) {
|
||||||
|
throw new EOFException("buffer underrun");
|
||||||
|
}
|
||||||
|
|
||||||
|
System.arraycopy(_plain, 0, _chunk, 0, totalBytes);
|
||||||
|
|
||||||
|
invokeCipher(totalBytes, _chunkSize > -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function for overriding the cipher invocation, i.e. XOR doesn't use a cipher
|
||||||
|
* and uses it's own implementation
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* @throws BadPaddingException
|
||||||
|
* @throws IllegalBlockSizeException
|
||||||
|
* @throws ShortBufferException
|
||||||
|
*/
|
||||||
|
protected int invokeCipher(int totalBytes, boolean doFinal) throws GeneralSecurityException {
|
||||||
|
if (doFinal) {
|
||||||
|
return _cipher.doFinal(_chunk, 0, totalBytes, _chunk);
|
||||||
|
} else {
|
||||||
|
return _cipher.update(_chunk, 0, totalBytes, _chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used when BIFF header fields (sid, size) are being read. The internal
|
||||||
|
* {@link Cipher} instance must step even when unencrypted bytes are read
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void readPlain(byte b[], int off, int len) {
|
||||||
|
if (len <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
int readBytes, total = 0;
|
||||||
|
do {
|
||||||
|
readBytes = read(b, off, len, true);
|
||||||
|
total += Math.max(0, readBytes);
|
||||||
|
} while (readBytes > -1 && total < len);
|
||||||
|
|
||||||
|
if (total < len) {
|
||||||
|
throw new EOFException("buffer underrun");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// need to wrap checked exception, because of LittleEndianInput interface :(
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some ciphers (actually just XOR) are based on the record size,
|
||||||
|
* which needs to be set before decryption
|
||||||
|
*
|
||||||
|
* @param recordSize the size of the next record
|
||||||
|
*/
|
||||||
|
public void setNextRecordSize(int recordSize) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the chunk bytes
|
||||||
|
*/
|
||||||
|
protected byte[] getChunk() {
|
||||||
|
return _chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the plain bytes
|
||||||
|
*/
|
||||||
|
protected byte[] getPlain() {
|
||||||
|
return _plain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the absolute position in the stream
|
||||||
|
*/
|
||||||
|
public long getPos() {
|
||||||
|
return _pos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,13 +25,18 @@ import java.io.FilterOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.BitSet;
|
||||||
|
|
||||||
|
import javax.crypto.BadPaddingException;
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
import javax.crypto.ShortBufferException;
|
||||||
|
|
||||||
import org.apache.poi.EncryptedDocumentException;
|
import org.apache.poi.EncryptedDocumentException;
|
||||||
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
||||||
import org.apache.poi.poifs.filesystem.POIFSWriterEvent;
|
import org.apache.poi.poifs.filesystem.POIFSWriterEvent;
|
||||||
import org.apache.poi.poifs.filesystem.POIFSWriterListener;
|
import org.apache.poi.poifs.filesystem.POIFSWriterListener;
|
||||||
|
import org.apache.poi.util.IOUtils;
|
||||||
import org.apache.poi.util.Internal;
|
import org.apache.poi.util.Internal;
|
||||||
import org.apache.poi.util.LittleEndian;
|
import org.apache.poi.util.LittleEndian;
|
||||||
import org.apache.poi.util.LittleEndianConsts;
|
import org.apache.poi.util.LittleEndianConsts;
|
||||||
@ -41,137 +46,246 @@ import org.apache.poi.util.TempFile;
|
|||||||
|
|
||||||
@Internal
|
@Internal
|
||||||
public abstract class ChunkedCipherOutputStream extends FilterOutputStream {
|
public abstract class ChunkedCipherOutputStream extends FilterOutputStream {
|
||||||
private static final POILogger logger = POILogFactory.getLogger(ChunkedCipherOutputStream.class);
|
private static final POILogger LOG = POILogFactory.getLogger(ChunkedCipherOutputStream.class);
|
||||||
|
private static final int STREAMING = -1;
|
||||||
protected final int chunkSize;
|
|
||||||
protected final int chunkMask;
|
private final int _chunkSize;
|
||||||
protected final int chunkBits;
|
private final int _chunkBits;
|
||||||
|
|
||||||
private final byte[] _chunk;
|
private final byte[] _chunk;
|
||||||
private final File fileOut;
|
private final BitSet _plainByteFlags;
|
||||||
private final DirectoryNode dir;
|
private final File _fileOut;
|
||||||
|
private final DirectoryNode _dir;
|
||||||
|
|
||||||
private long _pos = 0;
|
private long _pos = 0;
|
||||||
|
private long _totalPos = 0;
|
||||||
|
private long _written = 0;
|
||||||
private Cipher _cipher;
|
private Cipher _cipher;
|
||||||
|
|
||||||
public ChunkedCipherOutputStream(DirectoryNode dir, int chunkSize) throws IOException, GeneralSecurityException {
|
public ChunkedCipherOutputStream(DirectoryNode dir, int chunkSize) throws IOException, GeneralSecurityException {
|
||||||
super(null);
|
super(null);
|
||||||
this.chunkSize = chunkSize;
|
this._chunkSize = chunkSize;
|
||||||
chunkMask = chunkSize-1;
|
int cs = chunkSize == STREAMING ? 4096 : chunkSize;
|
||||||
chunkBits = Integer.bitCount(chunkMask);
|
_chunk = new byte[cs];
|
||||||
_chunk = new byte[chunkSize];
|
_plainByteFlags = new BitSet(cs);
|
||||||
|
_chunkBits = Integer.bitCount(cs-1);
|
||||||
fileOut = TempFile.createTempFile("encrypted_package", "crypt");
|
_fileOut = TempFile.createTempFile("encrypted_package", "crypt");
|
||||||
fileOut.deleteOnExit();
|
_fileOut.deleteOnExit();
|
||||||
this.out = new FileOutputStream(fileOut);
|
this.out = new FileOutputStream(_fileOut);
|
||||||
this.dir = dir;
|
this._dir = dir;
|
||||||
_cipher = initCipherForBlock(null, 0, false);
|
_cipher = initCipherForBlock(null, 0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ChunkedCipherOutputStream(OutputStream stream, int chunkSize) throws IOException, GeneralSecurityException {
|
||||||
|
super(stream);
|
||||||
|
this._chunkSize = chunkSize;
|
||||||
|
int cs = chunkSize == STREAMING ? 4096 : chunkSize;
|
||||||
|
_chunk = new byte[cs];
|
||||||
|
_plainByteFlags = new BitSet(cs);
|
||||||
|
_chunkBits = Integer.bitCount(cs-1);
|
||||||
|
_fileOut = null;
|
||||||
|
_dir = null;
|
||||||
|
_cipher = initCipherForBlock(null, 0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Cipher initCipherForBlock(int block, boolean lastChunk) throws IOException, GeneralSecurityException {
|
||||||
|
return initCipherForBlock(_cipher, block, lastChunk);
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract Cipher initCipherForBlock(Cipher existing, int block, boolean lastChunk)
|
protected abstract Cipher initCipherForBlock(Cipher existing, int block, boolean lastChunk)
|
||||||
throws GeneralSecurityException;
|
throws IOException, GeneralSecurityException;
|
||||||
|
|
||||||
@SuppressWarnings("hiding")
|
|
||||||
protected abstract void calculateChecksum(File fileOut, int oleStreamSize)
|
protected abstract void calculateChecksum(File fileOut, int oleStreamSize)
|
||||||
throws GeneralSecurityException, IOException;
|
throws GeneralSecurityException, IOException;
|
||||||
|
|
||||||
@SuppressWarnings("hiding")
|
|
||||||
protected abstract void createEncryptionInfoEntry(DirectoryNode dir, File tmpFile)
|
protected abstract void createEncryptionInfoEntry(DirectoryNode dir, File tmpFile)
|
||||||
throws IOException, GeneralSecurityException;
|
throws IOException, GeneralSecurityException;
|
||||||
|
|
||||||
|
@Override
|
||||||
public void write(int b) throws IOException {
|
public void write(int b) throws IOException {
|
||||||
write(new byte[]{(byte)b});
|
write(new byte[]{(byte)b});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void write(byte[] b) throws IOException {
|
public void write(byte[] b) throws IOException {
|
||||||
write(b, 0, b.length);
|
write(b, 0, b.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void write(byte[] b, int off, int len)
|
@Override
|
||||||
throws IOException {
|
public void write(byte[] b, int off, int len) throws IOException {
|
||||||
if (len == 0) return;
|
write(b, off, len, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writePlain(byte[] b, int off, int len) throws IOException {
|
||||||
|
write(b, off, len, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void write(byte[] b, int off, int len, boolean writePlain) throws IOException {
|
||||||
|
if (len == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (len < 0 || b.length < off+len) {
|
if (len < 0 || b.length < off+len) {
|
||||||
throw new IOException("not enough bytes in your input buffer");
|
throw new IOException("not enough bytes in your input buffer");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final int chunkMask = getChunkMask();
|
||||||
while (len > 0) {
|
while (len > 0) {
|
||||||
int posInChunk = (int)(_pos & chunkMask);
|
int posInChunk = (int)(_pos & chunkMask);
|
||||||
int nextLen = Math.min(chunkSize-posInChunk, len);
|
int nextLen = Math.min(_chunk.length-posInChunk, len);
|
||||||
System.arraycopy(b, off, _chunk, posInChunk, nextLen);
|
System.arraycopy(b, off, _chunk, posInChunk, nextLen);
|
||||||
|
if (writePlain) {
|
||||||
|
_plainByteFlags.set(posInChunk, posInChunk+nextLen);
|
||||||
|
}
|
||||||
_pos += nextLen;
|
_pos += nextLen;
|
||||||
|
_totalPos += nextLen;
|
||||||
off += nextLen;
|
off += nextLen;
|
||||||
len -= nextLen;
|
len -= nextLen;
|
||||||
if ((_pos & chunkMask) == 0) {
|
if ((_pos & chunkMask) == 0) {
|
||||||
try {
|
writeChunk(len > 0);
|
||||||
writeChunk();
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void writeChunk() throws IOException, GeneralSecurityException {
|
protected int getChunkMask() {
|
||||||
int posInChunk = (int)(_pos & chunkMask);
|
return _chunk.length-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void writeChunk(boolean continued) throws IOException {
|
||||||
|
if (_pos == 0 || _totalPos == _written) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int posInChunk = (int)(_pos & getChunkMask());
|
||||||
|
|
||||||
// normally posInChunk is 0, i.e. on the next chunk (-> index-1)
|
// normally posInChunk is 0, i.e. on the next chunk (-> index-1)
|
||||||
// but if called on close(), posInChunk is somewhere within the chunk data
|
// but if called on close(), posInChunk is somewhere within the chunk data
|
||||||
int index = (int)(_pos >> chunkBits);
|
int index = (int)(_pos >> _chunkBits);
|
||||||
boolean lastChunk;
|
boolean lastChunk;
|
||||||
if (posInChunk==0) {
|
if (posInChunk==0) {
|
||||||
index--;
|
index--;
|
||||||
posInChunk = chunkSize;
|
posInChunk = _chunk.length;
|
||||||
lastChunk = false;
|
lastChunk = false;
|
||||||
} else {
|
} else {
|
||||||
// pad the last chunk
|
// pad the last chunk
|
||||||
lastChunk = true;
|
lastChunk = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_cipher = initCipherForBlock(_cipher, index, lastChunk);
|
int ciLen;
|
||||||
|
try {
|
||||||
|
boolean doFinal = true;
|
||||||
|
long oldPos = _pos;
|
||||||
|
// reset stream (not only) in case we were interrupted by plain stream parts
|
||||||
|
// this also needs to be set to prevent an endless loop
|
||||||
|
_pos = 0;
|
||||||
|
if (_chunkSize == STREAMING) {
|
||||||
|
if (continued) {
|
||||||
|
doFinal = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_cipher = initCipherForBlock(_cipher, index, lastChunk);
|
||||||
|
// restore pos - only streaming chunks will be reset
|
||||||
|
_pos = oldPos;
|
||||||
|
}
|
||||||
|
ciLen = invokeCipher(posInChunk, doFinal);
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
throw new IOException("can't re-/initialize cipher", e);
|
||||||
|
}
|
||||||
|
|
||||||
int ciLen = _cipher.doFinal(_chunk, 0, posInChunk, _chunk);
|
|
||||||
out.write(_chunk, 0, ciLen);
|
out.write(_chunk, 0, ciLen);
|
||||||
|
_plainByteFlags.clear();
|
||||||
|
_written += ciLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function for overriding the cipher invocation, i.e. XOR doesn't use a cipher
|
||||||
|
* and uses it's own implementation
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* @throws BadPaddingException
|
||||||
|
* @throws IllegalBlockSizeException
|
||||||
|
* @throws ShortBufferException
|
||||||
|
*/
|
||||||
|
protected int invokeCipher(int posInChunk, boolean doFinal) throws GeneralSecurityException {
|
||||||
|
byte plain[] = (_plainByteFlags.isEmpty()) ? null : _chunk.clone();
|
||||||
|
|
||||||
|
int ciLen = (doFinal)
|
||||||
|
? _cipher.doFinal(_chunk, 0, posInChunk, _chunk)
|
||||||
|
: _cipher.update(_chunk, 0, posInChunk, _chunk);
|
||||||
|
|
||||||
|
for (int i = _plainByteFlags.nextSetBit(0); i >= 0 && i < posInChunk; i = _plainByteFlags.nextSetBit(i+1)) {
|
||||||
|
_chunk[i] = plain[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ciLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
try {
|
try {
|
||||||
writeChunk();
|
writeChunk(false);
|
||||||
|
|
||||||
super.close();
|
super.close();
|
||||||
|
|
||||||
int oleStreamSize = (int)(fileOut.length()+LittleEndianConsts.LONG_SIZE);
|
if (_fileOut != null) {
|
||||||
calculateChecksum(fileOut, (int)_pos);
|
int oleStreamSize = (int)(_fileOut.length()+LittleEndianConsts.LONG_SIZE);
|
||||||
dir.createDocument(DEFAULT_POIFS_ENTRY, oleStreamSize, new EncryptedPackageWriter());
|
calculateChecksum(_fileOut, (int)_pos);
|
||||||
createEncryptionInfoEntry(dir, fileOut);
|
_dir.createDocument(DEFAULT_POIFS_ENTRY, oleStreamSize, new EncryptedPackageWriter());
|
||||||
|
createEncryptionInfoEntry(_dir, _fileOut);
|
||||||
|
}
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected byte[] getChunk() {
|
||||||
|
return _chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected BitSet getPlainByteFlags() {
|
||||||
|
return _plainByteFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected long getPos() {
|
||||||
|
return _pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected long getTotalPos() {
|
||||||
|
return _totalPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some ciphers (actually just XOR) are based on the record size,
|
||||||
|
* which needs to be set before encryption
|
||||||
|
*
|
||||||
|
* @param recordSize the size of the next record
|
||||||
|
* @param isPlain {@code true} if the record is unencrypted
|
||||||
|
*/
|
||||||
|
public void setNextRecordSize(int recordSize, boolean isPlain) {
|
||||||
|
}
|
||||||
|
|
||||||
private class EncryptedPackageWriter implements POIFSWriterListener {
|
private class EncryptedPackageWriter implements POIFSWriterListener {
|
||||||
|
@Override
|
||||||
public void processPOIFSWriterEvent(POIFSWriterEvent event) {
|
public void processPOIFSWriterEvent(POIFSWriterEvent event) {
|
||||||
try {
|
try {
|
||||||
OutputStream os = event.getStream();
|
OutputStream os = event.getStream();
|
||||||
byte buf[] = new byte[chunkSize];
|
|
||||||
|
|
||||||
// StreamSize (8 bytes): An unsigned integer that specifies the number of bytes used by data
|
|
||||||
// encrypted within the EncryptedData field, not including the size of the StreamSize field.
|
|
||||||
// Note that the actual size of the \EncryptedPackage stream (1) can be larger than this
|
|
||||||
// value, depending on the block size of the chosen encryption algorithm
|
|
||||||
LittleEndian.putLong(buf, 0, _pos);
|
|
||||||
os.write(buf, 0, LittleEndian.LONG_SIZE);
|
|
||||||
|
|
||||||
FileInputStream fis = new FileInputStream(fileOut);
|
// StreamSize (8 bytes): An unsigned integer that specifies the number of bytes used by data
|
||||||
int readBytes;
|
// encrypted within the EncryptedData field, not including the size of the StreamSize field.
|
||||||
while ((readBytes = fis.read(buf)) != -1) {
|
// Note that the actual size of the \EncryptedPackage stream (1) can be larger than this
|
||||||
os.write(buf, 0, readBytes);
|
// value, depending on the block size of the chosen encryption algorithm
|
||||||
}
|
byte buf[] = new byte[LittleEndianConsts.LONG_SIZE];
|
||||||
|
LittleEndian.putLong(buf, 0, _pos);
|
||||||
|
os.write(buf);
|
||||||
|
|
||||||
|
FileInputStream fis = new FileInputStream(_fileOut);
|
||||||
|
IOUtils.copy(fis, os);
|
||||||
fis.close();
|
fis.close();
|
||||||
|
|
||||||
os.close();
|
os.close();
|
||||||
|
|
||||||
if (!fileOut.delete()) {
|
if (!_fileOut.delete()) {
|
||||||
logger.log(POILogger.ERROR, "Can't delete temporary encryption file: "+fileOut);
|
LOG.log(POILogger.ERROR, "Can't delete temporary encryption file: "+_fileOut);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new EncryptedDocumentException(e);
|
throw new EncryptedDocumentException(e);
|
||||||
|
@ -498,7 +498,9 @@ public class CryptoFunctions {
|
|||||||
* @return the byte array for xor obfuscation
|
* @return the byte array for xor obfuscation
|
||||||
*/
|
*/
|
||||||
public static byte[] createXorArray1(String password) {
|
public static byte[] createXorArray1(String password) {
|
||||||
if (password.length() > 15) password = password.substring(0, 15);
|
if (password.length() > 15) {
|
||||||
|
password = password.substring(0, 15);
|
||||||
|
}
|
||||||
byte passBytes[] = password.getBytes(Charset.forName("ASCII"));
|
byte passBytes[] = password.getBytes(Charset.forName("ASCII"));
|
||||||
|
|
||||||
// this code is based on the libre office implementation.
|
// this code is based on the libre office implementation.
|
||||||
|
@ -20,7 +20,9 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
import org.apache.poi.EncryptedDocumentException;
|
import org.apache.poi.EncryptedDocumentException;
|
||||||
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
||||||
@ -28,16 +30,15 @@ import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
|
|||||||
import org.apache.poi.poifs.filesystem.OPOIFSFileSystem;
|
import org.apache.poi.poifs.filesystem.OPOIFSFileSystem;
|
||||||
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
||||||
|
|
||||||
public abstract class Decryptor {
|
public abstract class Decryptor implements Cloneable {
|
||||||
public static final String DEFAULT_PASSWORD="VelvetSweatshop";
|
public static final String DEFAULT_PASSWORD="VelvetSweatshop";
|
||||||
public static final String DEFAULT_POIFS_ENTRY="EncryptedPackage";
|
public static final String DEFAULT_POIFS_ENTRY="EncryptedPackage";
|
||||||
|
|
||||||
protected final EncryptionInfoBuilder builder;
|
protected EncryptionInfo encryptionInfo;
|
||||||
private SecretKey secretKey;
|
private SecretKey secretKey;
|
||||||
private byte[] verifier, integrityHmacKey, integrityHmacValue;
|
private byte[] verifier, integrityHmacKey, integrityHmacValue;
|
||||||
|
|
||||||
protected Decryptor(EncryptionInfoBuilder builder) {
|
protected Decryptor() {
|
||||||
this.builder = builder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -54,6 +55,45 @@ public abstract class Decryptor {
|
|||||||
public abstract InputStream getDataStream(DirectoryNode dir)
|
public abstract InputStream getDataStream(DirectoryNode dir)
|
||||||
throws IOException, GeneralSecurityException;
|
throws IOException, GeneralSecurityException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a stream for decryption<p>
|
||||||
|
*
|
||||||
|
* As we are handling streams and don't know the total length beforehand,
|
||||||
|
* it's the callers duty to care for the length of the entries.
|
||||||
|
*
|
||||||
|
* @param stream the stream to be wrapped
|
||||||
|
* @param initialPos initial/current byte position within the stream
|
||||||
|
* @return decrypted stream
|
||||||
|
*/
|
||||||
|
public InputStream getDataStream(InputStream stream, int size, int initialPos)
|
||||||
|
throws IOException, GeneralSecurityException {
|
||||||
|
throw new RuntimeException("this decryptor doesn't support reading from a stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the chunk size of the data stream.
|
||||||
|
* Needs to be set before the data stream is requested.
|
||||||
|
* When not set, the implementation uses method specific default values
|
||||||
|
*
|
||||||
|
* @param chunkSize the chunk size, i.e. the block size with the same encryption key
|
||||||
|
*/
|
||||||
|
public void setChunkSize(int chunkSize) {
|
||||||
|
throw new RuntimeException("this decryptor doesn't support changing the chunk size");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a cipher object for a given block index for encryption
|
||||||
|
*
|
||||||
|
* @param cipher may be null, otherwise the given instance is reset to the new block index
|
||||||
|
* @param block the block index, e.g. the persist/slide id (hslf)
|
||||||
|
* @return a new cipher object, if cipher was null, otherwise the reinitialized cipher
|
||||||
|
* @throws GeneralSecurityException
|
||||||
|
*/
|
||||||
|
public Cipher initCipherForBlock(Cipher cipher, int block)
|
||||||
|
throws GeneralSecurityException {
|
||||||
|
throw new RuntimeException("this decryptor doesn't support initCipherForBlock");
|
||||||
|
}
|
||||||
|
|
||||||
public abstract boolean verifyPassword(String password)
|
public abstract boolean verifyPassword(String password)
|
||||||
throws GeneralSecurityException;
|
throws GeneralSecurityException;
|
||||||
|
|
||||||
@ -85,9 +125,11 @@ public abstract class Decryptor {
|
|||||||
public InputStream getDataStream(NPOIFSFileSystem fs) throws IOException, GeneralSecurityException {
|
public InputStream getDataStream(NPOIFSFileSystem fs) throws IOException, GeneralSecurityException {
|
||||||
return getDataStream(fs.getRoot());
|
return getDataStream(fs.getRoot());
|
||||||
}
|
}
|
||||||
|
|
||||||
public InputStream getDataStream(OPOIFSFileSystem fs) throws IOException, GeneralSecurityException {
|
public InputStream getDataStream(OPOIFSFileSystem fs) throws IOException, GeneralSecurityException {
|
||||||
return getDataStream(fs.getRoot());
|
return getDataStream(fs.getRoot());
|
||||||
}
|
}
|
||||||
|
|
||||||
public InputStream getDataStream(POIFSFileSystem fs) throws IOException, GeneralSecurityException {
|
public InputStream getDataStream(POIFSFileSystem fs) throws IOException, GeneralSecurityException {
|
||||||
return getDataStream(fs.getRoot());
|
return getDataStream(fs.getRoot());
|
||||||
}
|
}
|
||||||
@ -126,10 +168,29 @@ public abstract class Decryptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected int getBlockSizeInBytes() {
|
protected int getBlockSizeInBytes() {
|
||||||
return builder.getHeader().getBlockSize();
|
return encryptionInfo.getHeader().getBlockSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected int getKeySizeInBytes() {
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -16,12 +16,11 @@
|
|||||||
==================================================================== */
|
==================================================================== */
|
||||||
package org.apache.poi.poifs.crypt;
|
package org.apache.poi.poifs.crypt;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads and processes OOXML Encryption Headers
|
* Reads and processes OOXML Encryption Headers
|
||||||
* The constants are largely based on ZIP constants.
|
* 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_RC4 = CipherAlgorithm.rc4.ecmaId;
|
||||||
public static final int ALGORITHM_AES_128 = CipherAlgorithm.aes128.ecmaId;
|
public static final int ALGORITHM_AES_128 = CipherAlgorithm.aes128.ecmaId;
|
||||||
public static final int ALGORITHM_AES_192 = CipherAlgorithm.aes192.ecmaId;
|
public static final int ALGORITHM_AES_192 = CipherAlgorithm.aes192.ecmaId;
|
||||||
@ -132,4 +131,11 @@ public abstract class EncryptionHeader {
|
|||||||
protected void setCspName(String cspName) {
|
protected void setCspName(String cspName) {
|
||||||
this.cspName = cspName;
|
this.cspName = cspName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EncryptionHeader clone() throws CloneNotSupportedException {
|
||||||
|
EncryptionHeader other = (EncryptionHeader)super.clone();
|
||||||
|
other.keySalt = (keySalt == null) ? null : keySalt.clone();
|
||||||
|
return other;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.binaryRC4;
|
||||||
import static org.apache.poi.poifs.crypt.EncryptionMode.cryptoAPI;
|
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.standard;
|
||||||
|
import static org.apache.poi.poifs.crypt.EncryptionMode.xor;
|
||||||
|
|
||||||
import java.io.IOException;
|
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 versionMajor;
|
||||||
private final int versionMinor;
|
private final int versionMinor;
|
||||||
private final int encryptionFlags;
|
private final int encryptionFlags;
|
||||||
|
|
||||||
private final EncryptionHeader header;
|
private EncryptionHeader header;
|
||||||
private final EncryptionVerifier verifier;
|
private EncryptionVerifier verifier;
|
||||||
private final Decryptor decryptor;
|
private Decryptor decryptor;
|
||||||
private final Encryptor encryptor;
|
private Encryptor encryptor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A flag that specifies whether CryptoAPI RC4 or ECMA-376 encryption
|
* A flag that specifies whether CryptoAPI RC4 or ECMA-376 encryption
|
||||||
@ -75,50 +77,55 @@ public class EncryptionInfo {
|
|||||||
public EncryptionInfo(POIFSFileSystem fs) throws IOException {
|
public EncryptionInfo(POIFSFileSystem fs) throws IOException {
|
||||||
this(fs.getRoot());
|
this(fs.getRoot());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens for decryption
|
* Opens for decryption
|
||||||
*/
|
*/
|
||||||
public EncryptionInfo(OPOIFSFileSystem fs) throws IOException {
|
public EncryptionInfo(OPOIFSFileSystem fs) throws IOException {
|
||||||
this(fs.getRoot());
|
this(fs.getRoot());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens for decryption
|
* Opens for decryption
|
||||||
*/
|
*/
|
||||||
public EncryptionInfo(NPOIFSFileSystem fs) throws IOException {
|
public EncryptionInfo(NPOIFSFileSystem fs) throws IOException {
|
||||||
this(fs.getRoot());
|
this(fs.getRoot());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens for decryption
|
* Opens for decryption
|
||||||
*/
|
*/
|
||||||
public EncryptionInfo(DirectoryNode dir) throws IOException {
|
public EncryptionInfo(DirectoryNode dir) throws IOException {
|
||||||
this(dir.createDocumentInputStream("EncryptionInfo"), false);
|
this(dir.createDocumentInputStream("EncryptionInfo"), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public EncryptionInfo(LittleEndianInput dis, boolean isCryptoAPI) throws IOException {
|
public EncryptionInfo(LittleEndianInput dis, EncryptionMode preferredEncryptionMode) throws IOException {
|
||||||
final EncryptionMode encryptionMode;
|
if (preferredEncryptionMode == xor) {
|
||||||
versionMajor = dis.readShort();
|
versionMajor = xor.versionMajor;
|
||||||
versionMinor = dis.readShort();
|
versionMinor = xor.versionMinor;
|
||||||
|
} else {
|
||||||
|
versionMajor = dis.readUShort();
|
||||||
|
versionMinor = dis.readUShort();
|
||||||
|
}
|
||||||
|
|
||||||
if (!isCryptoAPI
|
if ( versionMajor == xor.versionMajor
|
||||||
&& versionMajor == binaryRC4.versionMajor
|
&& versionMinor == xor.versionMinor) {
|
||||||
|
encryptionMode = xor;
|
||||||
|
encryptionFlags = -1;
|
||||||
|
} else if ( versionMajor == binaryRC4.versionMajor
|
||||||
&& versionMinor == binaryRC4.versionMinor) {
|
&& versionMinor == binaryRC4.versionMinor) {
|
||||||
encryptionMode = binaryRC4;
|
encryptionMode = binaryRC4;
|
||||||
encryptionFlags = -1;
|
encryptionFlags = -1;
|
||||||
} else if (!isCryptoAPI
|
} else if (
|
||||||
&& versionMajor == agile.versionMajor
|
2 <= versionMajor && versionMajor <= 4
|
||||||
|
&& versionMinor == 2) {
|
||||||
|
encryptionMode = (preferredEncryptionMode == cryptoAPI) ? cryptoAPI : standard;
|
||||||
|
encryptionFlags = dis.readInt();
|
||||||
|
} else if (
|
||||||
|
versionMajor == agile.versionMajor
|
||||||
&& versionMinor == agile.versionMinor){
|
&& versionMinor == agile.versionMinor){
|
||||||
encryptionMode = agile;
|
encryptionMode = agile;
|
||||||
encryptionFlags = dis.readInt();
|
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 {
|
} else {
|
||||||
encryptionFlags = dis.readInt();
|
encryptionFlags = dis.readInt();
|
||||||
throw new EncryptedDocumentException(
|
throw new EncryptedDocumentException(
|
||||||
@ -138,10 +145,6 @@ public class EncryptionInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
eib.initialize(this, dis);
|
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
|
, int blockSize
|
||||||
, ChainingMode chainingMode
|
, ChainingMode chainingMode
|
||||||
) {
|
) {
|
||||||
|
this.encryptionMode = encryptionMode;
|
||||||
versionMajor = encryptionMode.versionMajor;
|
versionMajor = encryptionMode.versionMajor;
|
||||||
versionMinor = encryptionMode.versionMinor;
|
versionMinor = encryptionMode.versionMinor;
|
||||||
encryptionFlags = encryptionMode.encryptionFlags;
|
encryptionFlags = encryptionMode.encryptionFlags;
|
||||||
@ -187,11 +191,6 @@ public class EncryptionInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
eib.initialize(this, cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
|
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)
|
protected static EncryptionInfoBuilder getBuilder(EncryptionMode encryptionMode)
|
||||||
@ -229,4 +228,36 @@ public class EncryptionInfo {
|
|||||||
public Encryptor getEncryptor() {
|
public Encryptor getEncryptor() {
|
||||||
return encryptor;
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -30,24 +30,4 @@ public interface EncryptionInfoBuilder {
|
|||||||
* initialize the builder from scratch
|
* initialize the builder from scratch
|
||||||
*/
|
*/
|
||||||
void initialize(EncryptionInfo ei, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode);
|
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();
|
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,9 @@ public enum EncryptionMode {
|
|||||||
/* @see <a href="http://msdn.microsoft.com/en-us/library/dd906097(v=office.12).aspx">2.3.4.5 \EncryptionInfo Stream (Standard Encryption)</a> */
|
/* @see <a href="http://msdn.microsoft.com/en-us/library/dd906097(v=office.12).aspx">2.3.4.5 \EncryptionInfo Stream (Standard Encryption)</a> */
|
||||||
standard("org.apache.poi.poifs.crypt.standard.StandardEncryptionInfoBuilder", 4, 2, 0x24),
|
standard("org.apache.poi.poifs.crypt.standard.StandardEncryptionInfoBuilder", 4, 2, 0x24),
|
||||||
/* @see <a href="http://msdn.microsoft.com/en-us/library/dd925810(v=office.12).aspx">2.3.4.10 \EncryptionInfo Stream (Agile Encryption)</a> */
|
/* @see <a href="http://msdn.microsoft.com/en-us/library/dd925810(v=office.12).aspx">2.3.4.10 \EncryptionInfo Stream (Agile Encryption)</a> */
|
||||||
agile("org.apache.poi.poifs.crypt.agile.AgileEncryptionInfoBuilder", 4, 4, 0x40)
|
agile("org.apache.poi.poifs.crypt.agile.AgileEncryptionInfoBuilder", 4, 4, 0x40),
|
||||||
|
/* @see <a href="https://msdn.microsoft.com/en-us/library/dd907599(v=office.12).aspx">XOR Obfuscation</a> */
|
||||||
|
xor("org.apache.poi.poifs.crypt.xor.XOREncryptionInfoBuilder", 0, 0, 0)
|
||||||
;
|
;
|
||||||
|
|
||||||
public final String builder;
|
public final String builder;
|
||||||
|
@ -16,11 +16,10 @@
|
|||||||
==================================================================== */
|
==================================================================== */
|
||||||
package org.apache.poi.poifs.crypt;
|
package org.apache.poi.poifs.crypt;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used when checking if a key is valid for a document
|
* 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[] salt;
|
||||||
private byte[] encryptedVerifier;
|
private byte[] encryptedVerifier;
|
||||||
private byte[] encryptedVerifierHash;
|
private byte[] encryptedVerifierHash;
|
||||||
@ -105,5 +104,13 @@ public abstract class EncryptionVerifier {
|
|||||||
this.hashAlgorithm = hashAlgorithm;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,14 +21,16 @@ import java.io.OutputStream;
|
|||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
||||||
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
|
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
|
||||||
import org.apache.poi.poifs.filesystem.OPOIFSFileSystem;
|
import org.apache.poi.poifs.filesystem.OPOIFSFileSystem;
|
||||||
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
||||||
|
|
||||||
public abstract class Encryptor {
|
public abstract class Encryptor implements Cloneable {
|
||||||
protected static final String DEFAULT_POIFS_ENTRY = Decryptor.DEFAULT_POIFS_ENTRY;
|
protected static final String DEFAULT_POIFS_ENTRY = Decryptor.DEFAULT_POIFS_ENTRY;
|
||||||
|
private EncryptionInfo encryptionInfo;
|
||||||
private SecretKey secretKey;
|
private SecretKey secretKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -59,11 +61,43 @@ public abstract class Encryptor {
|
|||||||
return getDataStream(fs.getRoot());
|
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() {
|
public SecretKey getSecretKey() {
|
||||||
return secretKey;
|
return secretKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setSecretKey(SecretKey secretKey) {
|
public void setSecretKey(SecretKey secretKey) {
|
||||||
this.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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,31 +34,38 @@ import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
|||||||
import org.apache.poi.util.LittleEndian;
|
import org.apache.poi.util.LittleEndian;
|
||||||
import org.apache.poi.util.StringUtil;
|
import org.apache.poi.util.StringUtil;
|
||||||
|
|
||||||
public class BinaryRC4Decryptor extends Decryptor {
|
public class BinaryRC4Decryptor extends Decryptor implements Cloneable {
|
||||||
private long _length = -1L;
|
private long _length = -1L;
|
||||||
|
private int _chunkSize = 512;
|
||||||
|
|
||||||
private class BinaryRC4CipherInputStream extends ChunkedCipherInputStream {
|
private class BinaryRC4CipherInputStream extends ChunkedCipherInputStream {
|
||||||
|
|
||||||
|
@Override
|
||||||
protected Cipher initCipherForBlock(Cipher existing, int block)
|
protected Cipher initCipherForBlock(Cipher existing, int block)
|
||||||
throws GeneralSecurityException {
|
throws GeneralSecurityException {
|
||||||
return BinaryRC4Decryptor.initCipherForBlock(existing, block, builder, getSecretKey(), Cipher.DECRYPT_MODE);
|
return BinaryRC4Decryptor.this.initCipherForBlock(existing, block);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BinaryRC4CipherInputStream(DocumentInputStream stream, long size)
|
public BinaryRC4CipherInputStream(DocumentInputStream stream, long size)
|
||||||
throws GeneralSecurityException {
|
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) {
|
protected BinaryRC4Decryptor() {
|
||||||
super(builder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean verifyPassword(String password) {
|
public boolean verifyPassword(String password) {
|
||||||
EncryptionVerifier ver = builder.getVerifier();
|
EncryptionVerifier ver = getEncryptionInfo().getVerifier();
|
||||||
SecretKey skey = generateSecretKey(password, ver);
|
SecretKey skey = generateSecretKey(password, ver);
|
||||||
try {
|
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 encryptedVerifier[] = ver.getEncryptedVerifier();
|
||||||
byte verifier[] = new byte[encryptedVerifier.length];
|
byte verifier[] = new byte[encryptedVerifier.length];
|
||||||
cipher.update(encryptedVerifier, 0, encryptedVerifier.length, verifier);
|
cipher.update(encryptedVerifier, 0, encryptedVerifier.length, verifier);
|
||||||
@ -78,17 +85,23 @@ public class BinaryRC4Decryptor extends Decryptor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static Cipher initCipherForBlock(Cipher cipher, int block,
|
@Override
|
||||||
EncryptionInfoBuilder builder, SecretKey skey, int encryptMode)
|
public Cipher initCipherForBlock(Cipher cipher, int block)
|
||||||
throws GeneralSecurityException {
|
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();
|
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
|
||||||
byte blockKey[] = new byte[4];
|
byte blockKey[] = new byte[4];
|
||||||
LittleEndian.putUInt(blockKey, 0, block);
|
LittleEndian.putUInt(blockKey, 0, block);
|
||||||
byte encKey[] = CryptoFunctions.generateKey(skey.getEncoded(), hashAlgo, blockKey, 16);
|
byte encKey[] = CryptoFunctions.generateKey(skey.getEncoded(), hashAlgo, blockKey, 16);
|
||||||
SecretKey key = new SecretKeySpec(encKey, skey.getAlgorithm());
|
SecretKey key = new SecretKeySpec(encKey, skey.getAlgorithm());
|
||||||
if (cipher == null) {
|
if (cipher == null) {
|
||||||
EncryptionHeader em = builder.getHeader();
|
EncryptionHeader em = encryptionInfo.getHeader();
|
||||||
cipher = CryptoFunctions.getCipher(key, em.getCipherAlgorithm(), null, null, encryptMode);
|
cipher = CryptoFunctions.getCipher(key, em.getCipherAlgorithm(), null, null, encryptMode);
|
||||||
} else {
|
} else {
|
||||||
cipher.init(encryptMode, key);
|
cipher.init(encryptMode, key);
|
||||||
@ -96,10 +109,10 @@ public class BinaryRC4Decryptor extends Decryptor {
|
|||||||
return cipher;
|
return cipher;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static SecretKey generateSecretKey(String password,
|
protected static SecretKey generateSecretKey(String password, EncryptionVerifier ver) {
|
||||||
EncryptionVerifier ver) {
|
if (password.length() > 255) {
|
||||||
if (password.length() > 255)
|
|
||||||
password = password.substring(0, 255);
|
password = password.substring(0, 255);
|
||||||
|
}
|
||||||
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
|
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
|
||||||
MessageDigest hashAlg = CryptoFunctions.getMessageDigest(hashAlgo);
|
MessageDigest hashAlg = CryptoFunctions.getMessageDigest(hashAlgo);
|
||||||
byte hash[] = hashAlg.digest(StringUtil.getToUnicodeLE(password));
|
byte hash[] = hashAlg.digest(StringUtil.getToUnicodeLE(password));
|
||||||
@ -116,15 +129,23 @@ public class BinaryRC4Decryptor extends Decryptor {
|
|||||||
return skey;
|
return skey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
@SuppressWarnings("resource")
|
@SuppressWarnings("resource")
|
||||||
public InputStream getDataStream(DirectoryNode dir) throws IOException,
|
public ChunkedCipherInputStream getDataStream(DirectoryNode dir) throws IOException,
|
||||||
GeneralSecurityException {
|
GeneralSecurityException {
|
||||||
DocumentInputStream dis = dir.createDocumentInputStream(DEFAULT_POIFS_ENTRY);
|
DocumentInputStream dis = dir.createDocumentInputStream(DEFAULT_POIFS_ENTRY);
|
||||||
_length = dis.readLong();
|
_length = dis.readLong();
|
||||||
BinaryRC4CipherInputStream cipherStream = new BinaryRC4CipherInputStream(dis, _length);
|
return new BinaryRC4CipherInputStream(dis, _length);
|
||||||
return cipherStream;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getDataStream(InputStream stream, int size, int initialPos)
|
||||||
|
throws IOException, GeneralSecurityException {
|
||||||
|
return new BinaryRC4CipherInputStream(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
public long getLength() {
|
public long getLength() {
|
||||||
if (_length == -1L) {
|
if (_length == -1L) {
|
||||||
throw new IllegalStateException("Decryptor.getDataStream() was not called");
|
throw new IllegalStateException("Decryptor.getDataStream() was not called");
|
||||||
@ -132,4 +153,14 @@ public class BinaryRC4Decryptor extends Decryptor {
|
|||||||
|
|
||||||
return _length;
|
return _length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setChunkSize(int chunkSize) {
|
||||||
|
_chunkSize = chunkSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BinaryRC4Decryptor clone() throws CloneNotSupportedException {
|
||||||
|
return (BinaryRC4Decryptor)super.clone();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,7 @@ import org.apache.poi.poifs.crypt.HashAlgorithm;
|
|||||||
import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
|
import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
|
||||||
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
|
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
|
||||||
|
|
||||||
public class BinaryRC4EncryptionHeader extends EncryptionHeader implements
|
public class BinaryRC4EncryptionHeader extends EncryptionHeader implements EncryptionRecord, Cloneable {
|
||||||
EncryptionRecord {
|
|
||||||
|
|
||||||
protected BinaryRC4EncryptionHeader() {
|
protected BinaryRC4EncryptionHeader() {
|
||||||
setCipherAlgorithm(CipherAlgorithm.rc4);
|
setCipherAlgorithm(CipherAlgorithm.rc4);
|
||||||
@ -39,6 +38,14 @@ public class BinaryRC4EncryptionHeader extends EncryptionHeader implements
|
|||||||
setChainingMode(null);
|
setChainingMode(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void write(LittleEndianByteArrayOutputStream littleendianbytearrayoutputstream) {
|
public void write(LittleEndianByteArrayOutputStream littleendianbytearrayoutputstream) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BinaryRC4EncryptionHeader clone() throws CloneNotSupportedException {
|
||||||
|
return (BinaryRC4EncryptionHeader)super.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -23,55 +23,37 @@ import org.apache.poi.util.LittleEndianInput;
|
|||||||
|
|
||||||
public class BinaryRC4EncryptionInfoBuilder implements EncryptionInfoBuilder {
|
public class BinaryRC4EncryptionInfoBuilder implements EncryptionInfoBuilder {
|
||||||
|
|
||||||
EncryptionInfo info;
|
|
||||||
BinaryRC4EncryptionHeader header;
|
|
||||||
BinaryRC4EncryptionVerifier verifier;
|
|
||||||
BinaryRC4Decryptor decryptor;
|
|
||||||
BinaryRC4Encryptor encryptor;
|
|
||||||
|
|
||||||
public BinaryRC4EncryptionInfoBuilder() {
|
public BinaryRC4EncryptionInfoBuilder() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void initialize(EncryptionInfo info, LittleEndianInput dis)
|
public void initialize(EncryptionInfo info, LittleEndianInput dis)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
this.info = info;
|
|
||||||
int vMajor = info.getVersionMajor();
|
int vMajor = info.getVersionMajor();
|
||||||
int vMinor = info.getVersionMinor();
|
int vMinor = info.getVersionMinor();
|
||||||
assert (vMajor == 1 && vMinor == 1);
|
assert (vMajor == 1 && vMinor == 1);
|
||||||
|
|
||||||
header = new BinaryRC4EncryptionHeader();
|
info.setHeader(new BinaryRC4EncryptionHeader());
|
||||||
verifier = new BinaryRC4EncryptionVerifier(dis);
|
info.setVerifier(new BinaryRC4EncryptionVerifier(dis));
|
||||||
decryptor = new BinaryRC4Decryptor(this);
|
Decryptor dec = new BinaryRC4Decryptor();
|
||||||
encryptor = new BinaryRC4Encryptor(this);
|
dec.setEncryptionInfo(info);
|
||||||
|
info.setDecryptor(dec);
|
||||||
|
Encryptor enc = new BinaryRC4Encryptor();
|
||||||
|
enc.setEncryptionInfo(info);
|
||||||
|
info.setEncryptor(enc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void initialize(EncryptionInfo info,
|
public void initialize(EncryptionInfo info,
|
||||||
CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm,
|
CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm,
|
||||||
int keyBits, int blockSize, ChainingMode chainingMode) {
|
int keyBits, int blockSize, ChainingMode chainingMode) {
|
||||||
this.info = info;
|
info.setHeader(new BinaryRC4EncryptionHeader());
|
||||||
header = new BinaryRC4EncryptionHeader();
|
info.setVerifier(new BinaryRC4EncryptionVerifier());
|
||||||
verifier = new BinaryRC4EncryptionVerifier();
|
Decryptor dec = new BinaryRC4Decryptor();
|
||||||
decryptor = new BinaryRC4Decryptor(this);
|
dec.setEncryptionInfo(info);
|
||||||
encryptor = new BinaryRC4Encryptor(this);
|
info.setDecryptor(dec);
|
||||||
}
|
Encryptor enc = new BinaryRC4Encryptor();
|
||||||
|
enc.setEncryptionInfo(info);
|
||||||
public BinaryRC4EncryptionHeader getHeader() {
|
info.setEncryptor(enc);
|
||||||
return header;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BinaryRC4EncryptionVerifier getVerifier() {
|
|
||||||
return verifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BinaryRC4Decryptor getDecryptor() {
|
|
||||||
return decryptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BinaryRC4Encryptor getEncryptor() {
|
|
||||||
return encryptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EncryptionInfo getEncryptionInfo() {
|
|
||||||
return info;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
|
|||||||
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
|
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
|
||||||
import org.apache.poi.util.LittleEndianInput;
|
import org.apache.poi.util.LittleEndianInput;
|
||||||
|
|
||||||
public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements EncryptionRecord {
|
public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements EncryptionRecord, Cloneable {
|
||||||
|
|
||||||
protected BinaryRC4EncryptionVerifier() {
|
protected BinaryRC4EncryptionVerifier() {
|
||||||
setSpinCount(-1);
|
setSpinCount(-1);
|
||||||
@ -50,6 +50,7 @@ public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements E
|
|||||||
setHashAlgorithm(HashAlgorithm.md5);
|
setHashAlgorithm(HashAlgorithm.md5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected void setSalt(byte salt[]) {
|
protected void setSalt(byte salt[]) {
|
||||||
if (salt == null || salt.length != 16) {
|
if (salt == null || salt.length != 16) {
|
||||||
throw new EncryptedDocumentException("invalid verifier salt");
|
throw new EncryptedDocumentException("invalid verifier salt");
|
||||||
@ -58,14 +59,17 @@ public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements E
|
|||||||
super.setSalt(salt);
|
super.setSalt(salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected void setEncryptedVerifier(byte encryptedVerifier[]) {
|
protected void setEncryptedVerifier(byte encryptedVerifier[]) {
|
||||||
super.setEncryptedVerifier(encryptedVerifier);
|
super.setEncryptedVerifier(encryptedVerifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) {
|
protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) {
|
||||||
super.setEncryptedVerifierHash(encryptedVerifierHash);
|
super.setEncryptedVerifierHash(encryptedVerifierHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void write(LittleEndianByteArrayOutputStream bos) {
|
public void write(LittleEndianByteArrayOutputStream bos) {
|
||||||
byte salt[] = getSalt();
|
byte salt[] = getSalt();
|
||||||
assert (salt.length == 16);
|
assert (salt.length == 16);
|
||||||
@ -78,4 +82,8 @@ public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements E
|
|||||||
bos.write(encryptedVerifierHash);
|
bos.write(encryptedVerifierHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BinaryRC4EncryptionVerifier clone() throws CloneNotSupportedException {
|
||||||
|
return (BinaryRC4EncryptionVerifier)super.clone();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,39 +34,19 @@ import org.apache.poi.poifs.crypt.CryptoFunctions;
|
|||||||
import org.apache.poi.poifs.crypt.DataSpaceMapUtils;
|
import org.apache.poi.poifs.crypt.DataSpaceMapUtils;
|
||||||
import org.apache.poi.poifs.crypt.EncryptionInfo;
|
import org.apache.poi.poifs.crypt.EncryptionInfo;
|
||||||
import org.apache.poi.poifs.crypt.Encryptor;
|
import org.apache.poi.poifs.crypt.Encryptor;
|
||||||
|
import org.apache.poi.poifs.crypt.HashAlgorithm;
|
||||||
import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
|
import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
|
||||||
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
||||||
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
|
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 BinaryRC4Encryptor() {
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void confirmPassword(String password) {
|
public void confirmPassword(String password) {
|
||||||
Random r = new SecureRandom();
|
Random r = new SecureRandom();
|
||||||
byte salt[] = new byte[16];
|
byte salt[] = new byte[16];
|
||||||
@ -76,20 +56,20 @@ public class BinaryRC4Encryptor extends Encryptor {
|
|||||||
confirmPassword(password, null, null, verifier, salt, null);
|
confirmPassword(password, null, null, verifier, salt, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void confirmPassword(String password, byte keySpec[],
|
public void confirmPassword(String password, byte keySpec[],
|
||||||
byte keySalt[], byte verifier[], byte verifierSalt[],
|
byte keySalt[], byte verifier[], byte verifierSalt[],
|
||||||
byte integritySalt[]) {
|
byte integritySalt[]) {
|
||||||
BinaryRC4EncryptionVerifier ver = builder.getVerifier();
|
BinaryRC4EncryptionVerifier ver = (BinaryRC4EncryptionVerifier)getEncryptionInfo().getVerifier();
|
||||||
ver.setSalt(verifierSalt);
|
ver.setSalt(verifierSalt);
|
||||||
SecretKey skey = BinaryRC4Decryptor.generateSecretKey(password, ver);
|
SecretKey skey = BinaryRC4Decryptor.generateSecretKey(password, ver);
|
||||||
setSecretKey(skey);
|
setSecretKey(skey);
|
||||||
try {
|
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];
|
byte encryptedVerifier[] = new byte[16];
|
||||||
cipher.update(verifier, 0, 16, encryptedVerifier);
|
cipher.update(verifier, 0, 16, encryptedVerifier);
|
||||||
ver.setEncryptedVerifier(encryptedVerifier);
|
ver.setEncryptedVerifier(encryptedVerifier);
|
||||||
org.apache.poi.poifs.crypt.HashAlgorithm hashAlgo = ver
|
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
|
||||||
.getHashAlgorithm();
|
|
||||||
MessageDigest hashAlg = CryptoFunctions.getMessageDigest(hashAlgo);
|
MessageDigest hashAlg = CryptoFunctions.getMessageDigest(hashAlgo);
|
||||||
byte calcVerifierHash[] = hashAlg.digest(verifier);
|
byte calcVerifierHash[] = hashAlg.digest(verifier);
|
||||||
byte encryptedVerifierHash[] = cipher.doFinal(calcVerifierHash);
|
byte encryptedVerifierHash[] = cipher.doFinal(calcVerifierHash);
|
||||||
@ -99,22 +79,30 @@ public class BinaryRC4Encryptor extends Encryptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public OutputStream getDataStream(DirectoryNode dir)
|
public OutputStream getDataStream(DirectoryNode dir)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
OutputStream countStream = new BinaryRC4CipherOutputStream(dir);
|
OutputStream countStream = new BinaryRC4CipherOutputStream(dir);
|
||||||
return countStream;
|
return countStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BinaryRC4CipherOutputStream getDataStream(OutputStream stream, int initialOffset)
|
||||||
|
throws IOException, GeneralSecurityException {
|
||||||
|
return new BinaryRC4CipherOutputStream(stream);
|
||||||
|
}
|
||||||
|
|
||||||
protected int getKeySizeInBytes() {
|
protected int getKeySizeInBytes() {
|
||||||
return builder.getHeader().getKeySize() / 8;
|
return getEncryptionInfo().getHeader().getKeySize() / 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void createEncryptionInfoEntry(DirectoryNode dir) throws IOException {
|
protected void createEncryptionInfoEntry(DirectoryNode dir) throws IOException {
|
||||||
DataSpaceMapUtils.addDefaultDataSpace(dir);
|
DataSpaceMapUtils.addDefaultDataSpace(dir);
|
||||||
final EncryptionInfo info = builder.getEncryptionInfo();
|
final EncryptionInfo info = getEncryptionInfo();
|
||||||
final BinaryRC4EncryptionHeader header = builder.getHeader();
|
final BinaryRC4EncryptionHeader header = (BinaryRC4EncryptionHeader)info.getHeader();
|
||||||
final BinaryRC4EncryptionVerifier verifier = builder.getVerifier();
|
final BinaryRC4EncryptionVerifier verifier = (BinaryRC4EncryptionVerifier)info.getVerifier();
|
||||||
EncryptionRecord er = new EncryptionRecord() {
|
EncryptionRecord er = new EncryptionRecord() {
|
||||||
|
@Override
|
||||||
public void write(LittleEndianByteArrayOutputStream bos) {
|
public void write(LittleEndianByteArrayOutputStream bos) {
|
||||||
bos.writeShort(info.getVersionMajor());
|
bos.writeShort(info.getVersionMajor());
|
||||||
bos.writeShort(info.getVersionMinor());
|
bos.writeShort(info.getVersionMinor());
|
||||||
@ -124,4 +112,49 @@ public class BinaryRC4Encryptor extends Encryptor {
|
|||||||
};
|
};
|
||||||
DataSpaceMapUtils.createEncryptionEntry(dir, "EncryptionInfo", er);
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
package org.apache.poi.poifs.crypt.cryptoapi;
|
package org.apache.poi.poifs.crypt.cryptoapi;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@ -27,20 +26,20 @@ import java.util.Arrays;
|
|||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.ShortBufferException;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
import org.apache.poi.EncryptedDocumentException;
|
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.CryptoFunctions;
|
||||||
import org.apache.poi.poifs.crypt.Decryptor;
|
import org.apache.poi.poifs.crypt.Decryptor;
|
||||||
import org.apache.poi.poifs.crypt.EncryptionHeader;
|
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.EncryptionVerifier;
|
||||||
import org.apache.poi.poifs.crypt.HashAlgorithm;
|
import org.apache.poi.poifs.crypt.HashAlgorithm;
|
||||||
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
||||||
import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
||||||
import org.apache.poi.poifs.filesystem.DocumentNode;
|
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.BitField;
|
||||||
import org.apache.poi.util.BitFieldFactory;
|
import org.apache.poi.util.BitFieldFactory;
|
||||||
import org.apache.poi.util.BoundedInputStream;
|
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.LittleEndianInputStream;
|
||||||
import org.apache.poi.util.StringUtil;
|
import org.apache.poi.util.StringUtil;
|
||||||
|
|
||||||
public class CryptoAPIDecryptor extends Decryptor {
|
public class CryptoAPIDecryptor extends Decryptor implements Cloneable {
|
||||||
|
|
||||||
private long _length;
|
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 class StreamDescriptorEntry {
|
||||||
static BitField flagStream = BitFieldFactory.getInstance(1);
|
static BitField flagStream = BitFieldFactory.getInstance(1);
|
||||||
|
|
||||||
@ -111,16 +64,16 @@ public class CryptoAPIDecryptor extends Decryptor {
|
|||||||
String streamName;
|
String streamName;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected CryptoAPIDecryptor(CryptoAPIEncryptionInfoBuilder builder) {
|
protected CryptoAPIDecryptor() {
|
||||||
super(builder);
|
|
||||||
_length = -1L;
|
_length = -1L;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean verifyPassword(String password) {
|
public boolean verifyPassword(String password) {
|
||||||
EncryptionVerifier ver = builder.getVerifier();
|
EncryptionVerifier ver = getEncryptionInfo().getVerifier();
|
||||||
SecretKey skey = generateSecretKey(password, ver);
|
SecretKey skey = generateSecretKey(password, ver);
|
||||||
try {
|
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 encryptedVerifier[] = ver.getEncryptedVerifier();
|
||||||
byte verifier[] = new byte[encryptedVerifier.length];
|
byte verifier[] = new byte[encryptedVerifier.length];
|
||||||
cipher.update(encryptedVerifier, 0, encryptedVerifier.length, verifier);
|
cipher.update(encryptedVerifier, 0, encryptedVerifier.length, verifier);
|
||||||
@ -140,30 +93,25 @@ public class CryptoAPIDecryptor extends Decryptor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
public Cipher initCipherForBlock(Cipher cipher, int block)
|
public Cipher initCipherForBlock(Cipher cipher, int block)
|
||||||
throws GeneralSecurityException {
|
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,
|
protected static Cipher initCipherForBlock(Cipher cipher, int block,
|
||||||
EncryptionInfoBuilder builder, SecretKey skey, int encryptMode)
|
EncryptionInfo encryptionInfo, SecretKey skey, int encryptMode)
|
||||||
throws GeneralSecurityException {
|
throws GeneralSecurityException {
|
||||||
EncryptionVerifier ver = builder.getVerifier();
|
EncryptionVerifier ver = encryptionInfo.getVerifier();
|
||||||
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
|
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
|
||||||
byte blockKey[] = new byte[4];
|
byte blockKey[] = new byte[4];
|
||||||
LittleEndian.putUInt(blockKey, 0, block);
|
LittleEndian.putUInt(blockKey, 0, block);
|
||||||
MessageDigest hashAlg = CryptoFunctions.getMessageDigest(hashAlgo);
|
MessageDigest hashAlg = CryptoFunctions.getMessageDigest(hashAlgo);
|
||||||
hashAlg.update(skey.getEncoded());
|
hashAlg.update(skey.getEncoded());
|
||||||
byte encKey[] = hashAlg.digest(blockKey);
|
byte encKey[] = hashAlg.digest(blockKey);
|
||||||
EncryptionHeader header = builder.getHeader();
|
EncryptionHeader header = encryptionInfo.getHeader();
|
||||||
int keyBits = header.getKeySize();
|
int keyBits = header.getKeySize();
|
||||||
encKey = CryptoFunctions.getBlock0(encKey, keyBits / 8);
|
encKey = CryptoFunctions.getBlock0(encKey, keyBits / 8);
|
||||||
if (keyBits == 40) {
|
if (keyBits == 40) {
|
||||||
@ -190,6 +138,18 @@ public class CryptoAPIDecryptor extends Decryptor {
|
|||||||
return skey;
|
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.
|
* Decrypt the Document-/SummaryInformation and other optionally streams.
|
||||||
* Opposed to other crypto modes, cryptoapi is record based and can't be used
|
* Opposed to other crypto modes, cryptoapi is record based and can't be used
|
||||||
@ -197,15 +157,17 @@ public class CryptoAPIDecryptor extends Decryptor {
|
|||||||
*
|
*
|
||||||
* @see <a href="http://msdn.microsoft.com/en-us/library/dd943321(v=office.12).aspx">2.3.5.4 RC4 CryptoAPI Encrypted Summary Stream</a>
|
* @see <a href="http://msdn.microsoft.com/en-us/library/dd943321(v=office.12).aspx">2.3.5.4 RC4 CryptoAPI Encrypted Summary Stream</a>
|
||||||
*/
|
*/
|
||||||
public InputStream getDataStream(DirectoryNode dir)
|
public POIFSFileSystem getSummaryEntries(DirectoryNode root, String encryptedStream)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
NPOIFSFileSystem fsOut = new NPOIFSFileSystem();
|
POIFSFileSystem fsOut = new POIFSFileSystem();
|
||||||
DocumentNode es = (DocumentNode) dir.getEntry("EncryptedSummary");
|
// HSLF: encryptedStream
|
||||||
DocumentInputStream dis = dir.createDocumentInputStream(es);
|
// HSSF: encryption
|
||||||
|
DocumentNode es = (DocumentNode) root.getEntry(encryptedStream);
|
||||||
|
DocumentInputStream dis = root.createDocumentInputStream(es);
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
IOUtils.copy(dis, bos);
|
IOUtils.copy(dis, bos);
|
||||||
dis.close();
|
dis.close();
|
||||||
SeekableByteArrayInputStream sbis = new SeekableByteArrayInputStream(bos.toByteArray());
|
CryptoAPIDocumentInputStream sbis = new CryptoAPIDocumentInputStream(this, bos.toByteArray());
|
||||||
LittleEndianInputStream leis = new LittleEndianInputStream(sbis);
|
LittleEndianInputStream leis = new LittleEndianInputStream(sbis);
|
||||||
int streamDescriptorArrayOffset = (int) leis.readUInt();
|
int streamDescriptorArrayOffset = (int) leis.readUInt();
|
||||||
/* int streamDescriptorArraySize = (int) */ leis.readUInt();
|
/* int streamDescriptorArraySize = (int) */ leis.readUInt();
|
||||||
@ -239,21 +201,40 @@ public class CryptoAPIDecryptor extends Decryptor {
|
|||||||
leis.close();
|
leis.close();
|
||||||
sbis.close();
|
sbis.close();
|
||||||
sbis = null;
|
sbis = null;
|
||||||
bos.reset();
|
return fsOut;
|
||||||
fsOut.writeFilesystem(bos);
|
|
||||||
fsOut.close();
|
|
||||||
_length = bos.size();
|
|
||||||
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
|
|
||||||
return bis;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the length of the stream returned by {@link #getDataStream(DirectoryNode)}
|
* @return the length of the stream returned by {@link #getDataStream(DirectoryNode)}
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public long getLength() {
|
public long getLength() {
|
||||||
if (_length == -1L) {
|
if (_length == -1L) {
|
||||||
throw new IllegalStateException("Decryptor.getDataStream() was not called");
|
throw new IllegalStateException("Decryptor.getDataStream() was not called");
|
||||||
}
|
}
|
||||||
return _length;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -27,7 +27,7 @@ import org.apache.poi.poifs.crypt.HashAlgorithm;
|
|||||||
import org.apache.poi.poifs.crypt.standard.StandardEncryptionHeader;
|
import org.apache.poi.poifs.crypt.standard.StandardEncryptionHeader;
|
||||||
import org.apache.poi.util.LittleEndianInput;
|
import org.apache.poi.util.LittleEndianInput;
|
||||||
|
|
||||||
public class CryptoAPIEncryptionHeader extends StandardEncryptionHeader {
|
public class CryptoAPIEncryptionHeader extends StandardEncryptionHeader implements Cloneable {
|
||||||
|
|
||||||
public CryptoAPIEncryptionHeader(LittleEndianInput is) throws IOException {
|
public CryptoAPIEncryptionHeader(LittleEndianInput is) throws IOException {
|
||||||
super(is);
|
super(is);
|
||||||
@ -39,6 +39,7 @@ public class CryptoAPIEncryptionHeader extends StandardEncryptionHeader {
|
|||||||
super(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
|
super(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void setKeySize(int keyBits) {
|
public void setKeySize(int keyBits) {
|
||||||
// Microsoft Base Cryptographic Provider is limited up to 40 bits
|
// Microsoft Base Cryptographic Provider is limited up to 40 bits
|
||||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa375599(v=vs.85).aspx
|
// 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);
|
setCspName(CipherProvider.rc4.cipherProviderName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CryptoAPIEncryptionHeader clone() throws CloneNotSupportedException {
|
||||||
|
return (CryptoAPIEncryptionHeader)super.clone();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,63 +23,52 @@ import org.apache.poi.poifs.crypt.*;
|
|||||||
import org.apache.poi.util.LittleEndianInput;
|
import org.apache.poi.util.LittleEndianInput;
|
||||||
|
|
||||||
public class CryptoAPIEncryptionInfoBuilder implements EncryptionInfoBuilder {
|
public class CryptoAPIEncryptionInfoBuilder implements EncryptionInfoBuilder {
|
||||||
EncryptionInfo info;
|
|
||||||
CryptoAPIEncryptionHeader header;
|
|
||||||
CryptoAPIEncryptionVerifier verifier;
|
|
||||||
CryptoAPIDecryptor decryptor;
|
|
||||||
CryptoAPIEncryptor encryptor;
|
|
||||||
|
|
||||||
public CryptoAPIEncryptionInfoBuilder() {
|
public CryptoAPIEncryptionInfoBuilder() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* initialize the builder from a stream
|
* initialize the builder from a stream
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void initialize(EncryptionInfo info, LittleEndianInput dis)
|
public void initialize(EncryptionInfo info, LittleEndianInput dis)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
this.info = info;
|
|
||||||
/* int hSize = */ dis.readInt();
|
/* int hSize = */ dis.readInt();
|
||||||
header = new CryptoAPIEncryptionHeader(dis);
|
CryptoAPIEncryptionHeader header = new CryptoAPIEncryptionHeader(dis);
|
||||||
verifier = new CryptoAPIEncryptionVerifier(dis, header);
|
info.setHeader(header);
|
||||||
decryptor = new CryptoAPIDecryptor(this);
|
info.setVerifier(new CryptoAPIEncryptionVerifier(dis, header));
|
||||||
encryptor = new CryptoAPIEncryptor(this);
|
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
|
* initialize the builder from scratch
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void initialize(EncryptionInfo info,
|
public void initialize(EncryptionInfo info,
|
||||||
CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm,
|
CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm,
|
||||||
int keyBits, int blockSize, ChainingMode chainingMode) {
|
int keyBits, int blockSize, ChainingMode chainingMode) {
|
||||||
this.info = info;
|
if (cipherAlgorithm == null) {
|
||||||
if (cipherAlgorithm == null) cipherAlgorithm = CipherAlgorithm.rc4;
|
cipherAlgorithm = CipherAlgorithm.rc4;
|
||||||
if (hashAlgorithm == null) hashAlgorithm = HashAlgorithm.sha1;
|
}
|
||||||
if (keyBits == -1) keyBits = 0x28;
|
if (hashAlgorithm == null) {
|
||||||
|
hashAlgorithm = HashAlgorithm.sha1;
|
||||||
|
}
|
||||||
|
if (keyBits == -1) {
|
||||||
|
keyBits = 0x28;
|
||||||
|
}
|
||||||
assert(cipherAlgorithm == CipherAlgorithm.rc4 && hashAlgorithm == HashAlgorithm.sha1);
|
assert(cipherAlgorithm == CipherAlgorithm.rc4 && hashAlgorithm == HashAlgorithm.sha1);
|
||||||
|
|
||||||
header = new CryptoAPIEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
|
info.setHeader(new CryptoAPIEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
|
||||||
verifier = new CryptoAPIEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
|
info.setVerifier(new CryptoAPIEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
|
||||||
decryptor = new CryptoAPIDecryptor(this);
|
CryptoAPIDecryptor dec = new CryptoAPIDecryptor();
|
||||||
encryptor = new CryptoAPIEncryptor(this);
|
dec.setEncryptionInfo(info);
|
||||||
}
|
info.setDecryptor(dec);
|
||||||
|
CryptoAPIEncryptor enc = new CryptoAPIEncryptor();
|
||||||
public CryptoAPIEncryptionHeader getHeader() {
|
enc.setEncryptionInfo(info);
|
||||||
return header;
|
info.setEncryptor(enc);
|
||||||
}
|
|
||||||
|
|
||||||
public CryptoAPIEncryptionVerifier getVerifier() {
|
|
||||||
return verifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CryptoAPIDecryptor getDecryptor() {
|
|
||||||
return decryptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CryptoAPIEncryptor getEncryptor() {
|
|
||||||
return encryptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EncryptionInfo getEncryptionInfo() {
|
|
||||||
return info;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ import org.apache.poi.poifs.crypt.HashAlgorithm;
|
|||||||
import org.apache.poi.poifs.crypt.standard.StandardEncryptionVerifier;
|
import org.apache.poi.poifs.crypt.standard.StandardEncryptionVerifier;
|
||||||
import org.apache.poi.util.LittleEndianInput;
|
import org.apache.poi.util.LittleEndianInput;
|
||||||
|
|
||||||
public class CryptoAPIEncryptionVerifier extends StandardEncryptionVerifier {
|
public class CryptoAPIEncryptionVerifier extends StandardEncryptionVerifier implements Cloneable {
|
||||||
|
|
||||||
protected CryptoAPIEncryptionVerifier(LittleEndianInput is,
|
protected CryptoAPIEncryptionVerifier(LittleEndianInput is,
|
||||||
CryptoAPIEncryptionHeader header) {
|
CryptoAPIEncryptionHeader header) {
|
||||||
@ -36,15 +36,23 @@ public class CryptoAPIEncryptionVerifier extends StandardEncryptionVerifier {
|
|||||||
super(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
|
super(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected void setSalt(byte salt[]) {
|
protected void setSalt(byte salt[]) {
|
||||||
super.setSalt(salt);
|
super.setSalt(salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected void setEncryptedVerifier(byte encryptedVerifier[]) {
|
protected void setEncryptedVerifier(byte encryptedVerifier[]) {
|
||||||
super.setEncryptedVerifier(encryptedVerifier);
|
super.setEncryptedVerifier(encryptedVerifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) {
|
protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) {
|
||||||
super.setEncryptedVerifierHash(encryptedVerifierHash);
|
super.setEncryptedVerifierHash(encryptedVerifierHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CryptoAPIEncryptionVerifier clone() throws CloneNotSupportedException {
|
||||||
|
return (CryptoAPIEncryptionVerifier)super.clone();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
package org.apache.poi.poifs.crypt.cryptoapi;
|
package org.apache.poi.poifs.crypt.cryptoapi;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.security.GeneralSecurityException;
|
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.PropertySetFactory;
|
||||||
import org.apache.poi.hpsf.SummaryInformation;
|
import org.apache.poi.hpsf.SummaryInformation;
|
||||||
import org.apache.poi.hpsf.WritingNotSupportedException;
|
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.CryptoFunctions;
|
||||||
import org.apache.poi.poifs.crypt.DataSpaceMapUtils;
|
import org.apache.poi.poifs.crypt.DataSpaceMapUtils;
|
||||||
import org.apache.poi.poifs.crypt.EncryptionInfo;
|
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.LittleEndianByteArrayOutputStream;
|
||||||
import org.apache.poi.util.StringUtil;
|
import org.apache.poi.util.StringUtil;
|
||||||
|
|
||||||
public class CryptoAPIEncryptor extends Encryptor {
|
public class CryptoAPIEncryptor extends Encryptor implements Cloneable {
|
||||||
private final CryptoAPIEncryptionInfoBuilder builder;
|
|
||||||
|
private int _chunkSize = 512;
|
||||||
protected CryptoAPIEncryptor(CryptoAPIEncryptionInfoBuilder builder) {
|
|
||||||
this.builder = builder;
|
protected CryptoAPIEncryptor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void confirmPassword(String password) {
|
public void confirmPassword(String password) {
|
||||||
Random r = new SecureRandom();
|
Random r = new SecureRandom();
|
||||||
byte salt[] = new byte[16];
|
byte salt[] = new byte[16];
|
||||||
@ -66,11 +68,12 @@ public class CryptoAPIEncryptor extends Encryptor {
|
|||||||
confirmPassword(password, null, null, verifier, salt, null);
|
confirmPassword(password, null, null, verifier, salt, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void confirmPassword(String password, byte keySpec[],
|
public void confirmPassword(String password, byte keySpec[],
|
||||||
byte keySalt[], byte verifier[], byte verifierSalt[],
|
byte keySalt[], byte verifier[], byte verifierSalt[],
|
||||||
byte integritySalt[]) {
|
byte integritySalt[]) {
|
||||||
assert(verifier != null && verifierSalt != null);
|
assert(verifier != null && verifierSalt != null);
|
||||||
CryptoAPIEncryptionVerifier ver = builder.getVerifier();
|
CryptoAPIEncryptionVerifier ver = (CryptoAPIEncryptionVerifier)getEncryptionInfo().getVerifier();
|
||||||
ver.setSalt(verifierSalt);
|
ver.setSalt(verifierSalt);
|
||||||
SecretKey skey = CryptoAPIDecryptor.generateSecretKey(password, ver);
|
SecretKey skey = CryptoAPIDecryptor.generateSecretKey(password, ver);
|
||||||
setSecretKey(skey);
|
setSecretKey(skey);
|
||||||
@ -99,8 +102,20 @@ public class CryptoAPIEncryptor extends Encryptor {
|
|||||||
*/
|
*/
|
||||||
public Cipher initCipherForBlock(Cipher cipher, int block)
|
public Cipher initCipherForBlock(Cipher cipher, int block)
|
||||||
throws GeneralSecurityException {
|
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.
|
* Encrypt the Document-/SummaryInformation and other optionally streams.
|
||||||
@ -109,9 +124,9 @@ public class CryptoAPIEncryptor extends Encryptor {
|
|||||||
*
|
*
|
||||||
* @see <a href="http://msdn.microsoft.com/en-us/library/dd943321(v=office.12).aspx">2.3.5.4 RC4 CryptoAPI Encrypted Summary Stream</a>
|
* @see <a href="http://msdn.microsoft.com/en-us/library/dd943321(v=office.12).aspx">2.3.5.4 RC4 CryptoAPI Encrypted Summary Stream</a>
|
||||||
*/
|
*/
|
||||||
public OutputStream getDataStream(DirectoryNode dir)
|
public OutputStream getSummaryEntries(DirectoryNode dir)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
CipherByteArrayOutputStream bos = new CipherByteArrayOutputStream();
|
CryptoAPIDocumentOutputStream bos = new CryptoAPIDocumentOutputStream(this);
|
||||||
byte buf[] = new byte[8];
|
byte buf[] = new byte[8];
|
||||||
|
|
||||||
bos.write(buf, 0, 8); // skip header
|
bos.write(buf, 0, 8); // skip header
|
||||||
@ -124,7 +139,9 @@ public class CryptoAPIEncryptor extends Encryptor {
|
|||||||
|
|
||||||
int block = 0;
|
int block = 0;
|
||||||
for (String entryName : entryNames) {
|
for (String entryName : entryNames) {
|
||||||
if (!dir.hasEntry(entryName)) continue;
|
if (!dir.hasEntry(entryName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
StreamDescriptorEntry descEntry = new StreamDescriptorEntry();
|
StreamDescriptorEntry descEntry = new StreamDescriptorEntry();
|
||||||
descEntry.block = block;
|
descEntry.block = block;
|
||||||
descEntry.streamOffset = bos.size();
|
descEntry.streamOffset = bos.size();
|
||||||
@ -193,15 +210,21 @@ public class CryptoAPIEncryptor extends Encryptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected int getKeySizeInBytes() {
|
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 {
|
protected void createEncryptionInfoEntry(DirectoryNode dir) throws IOException {
|
||||||
DataSpaceMapUtils.addDefaultDataSpace(dir);
|
DataSpaceMapUtils.addDefaultDataSpace(dir);
|
||||||
final EncryptionInfo info = builder.getEncryptionInfo();
|
final EncryptionInfo info = getEncryptionInfo();
|
||||||
final CryptoAPIEncryptionHeader header = builder.getHeader();
|
final CryptoAPIEncryptionHeader header = (CryptoAPIEncryptionHeader)getEncryptionInfo().getHeader();
|
||||||
final CryptoAPIEncryptionVerifier verifier = builder.getVerifier();
|
final CryptoAPIEncryptionVerifier verifier = (CryptoAPIEncryptionVerifier)getEncryptionInfo().getVerifier();
|
||||||
EncryptionRecord er = new EncryptionRecord() {
|
EncryptionRecord er = new EncryptionRecord() {
|
||||||
|
@Override
|
||||||
public void write(LittleEndianByteArrayOutputStream bos) {
|
public void write(LittleEndianByteArrayOutputStream bos) {
|
||||||
bos.writeShort(info.getVersionMajor());
|
bos.writeShort(info.getVersionMajor());
|
||||||
bos.writeShort(info.getVersionMinor());
|
bos.writeShort(info.getVersionMinor());
|
||||||
@ -212,44 +235,43 @@ public class CryptoAPIEncryptor extends Encryptor {
|
|||||||
DataSpaceMapUtils.createEncryptionEntry(dir, "EncryptionInfo", er);
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,6 @@ import org.apache.poi.poifs.crypt.ChainingMode;
|
|||||||
import org.apache.poi.poifs.crypt.CryptoFunctions;
|
import org.apache.poi.poifs.crypt.CryptoFunctions;
|
||||||
import org.apache.poi.poifs.crypt.Decryptor;
|
import org.apache.poi.poifs.crypt.Decryptor;
|
||||||
import org.apache.poi.poifs.crypt.EncryptionHeader;
|
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.EncryptionVerifier;
|
||||||
import org.apache.poi.poifs.crypt.HashAlgorithm;
|
import org.apache.poi.poifs.crypt.HashAlgorithm;
|
||||||
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
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;
|
private long _length = -1;
|
||||||
|
|
||||||
protected StandardDecryptor(EncryptionInfoBuilder builder) {
|
protected StandardDecryptor() {
|
||||||
super(builder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean verifyPassword(String password) {
|
public boolean verifyPassword(String password) {
|
||||||
EncryptionVerifier ver = builder.getVerifier();
|
EncryptionVerifier ver = getEncryptionInfo().getVerifier();
|
||||||
SecretKey skey = generateSecretKey(password, ver, getKeySizeInBytes());
|
SecretKey skey = generateSecretKey(password, ver, getKeySizeInBytes());
|
||||||
Cipher cipher = getCipher(skey);
|
Cipher cipher = getCipher(skey);
|
||||||
|
|
||||||
@ -116,12 +115,13 @@ public class StandardDecryptor extends Decryptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Cipher getCipher(SecretKey key) {
|
private Cipher getCipher(SecretKey key) {
|
||||||
EncryptionHeader em = builder.getHeader();
|
EncryptionHeader em = getEncryptionInfo().getHeader();
|
||||||
ChainingMode cm = em.getChainingMode();
|
ChainingMode cm = em.getChainingMode();
|
||||||
assert(cm == ChainingMode.ecb);
|
assert(cm == ChainingMode.ecb);
|
||||||
return CryptoFunctions.getCipher(key, em.getCipherAlgorithm(), cm, null, Cipher.DECRYPT_MODE);
|
return CryptoFunctions.getCipher(key, em.getCipherAlgorithm(), cm, null, Cipher.DECRYPT_MODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
@SuppressWarnings("resource")
|
@SuppressWarnings("resource")
|
||||||
public InputStream getDataStream(DirectoryNode dir) throws IOException {
|
public InputStream getDataStream(DirectoryNode dir) throws IOException {
|
||||||
DocumentInputStream dis = dir.createDocumentInputStream(DEFAULT_POIFS_ENTRY);
|
DocumentInputStream dis = dir.createDocumentInputStream(DEFAULT_POIFS_ENTRY);
|
||||||
@ -134,7 +134,7 @@ public class StandardDecryptor extends Decryptor {
|
|||||||
// limit wrong calculated ole entries - (bug #57080)
|
// limit wrong calculated ole entries - (bug #57080)
|
||||||
// standard encryption always uses aes encoding, so blockSize is always 16
|
// standard encryption always uses aes encoding, so blockSize is always 16
|
||||||
// http://stackoverflow.com/questions/3283787/size-of-data-after-aes-encryption
|
// 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;
|
long cipherLen = (_length/blockSize + 1) * blockSize;
|
||||||
Cipher cipher = getCipher(getSecretKey());
|
Cipher cipher = getCipher(getSecretKey());
|
||||||
|
|
||||||
@ -145,8 +145,16 @@ public class StandardDecryptor extends Decryptor {
|
|||||||
/**
|
/**
|
||||||
* @return the length of the stream returned by {@link #getDataStream(DirectoryNode)}
|
* @return the length of the stream returned by {@link #getDataStream(DirectoryNode)}
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public long getLength(){
|
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;
|
return _length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StandardDecryptor clone() throws CloneNotSupportedException {
|
||||||
|
return (StandardDecryptor)super.clone();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import static org.apache.poi.poifs.crypt.EncryptionInfo.flagCryptoAPI;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import org.apache.poi.hssf.record.RecordInputStream;
|
||||||
import org.apache.poi.poifs.crypt.ChainingMode;
|
import org.apache.poi.poifs.crypt.ChainingMode;
|
||||||
import org.apache.poi.poifs.crypt.CipherAlgorithm;
|
import org.apache.poi.poifs.crypt.CipherAlgorithm;
|
||||||
import org.apache.poi.poifs.crypt.CipherProvider;
|
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.LittleEndianOutput;
|
||||||
import org.apache.poi.util.StringUtil;
|
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 {
|
protected StandardEncryptionHeader(LittleEndianInput is) throws IOException {
|
||||||
setFlags(is.readInt());
|
setFlags(is.readInt());
|
||||||
@ -55,9 +56,17 @@ public class StandardEncryptionHeader extends EncryptionHeader implements Encryp
|
|||||||
|
|
||||||
// CSPName may not always be specified
|
// CSPName may not always be specified
|
||||||
// In some cases, the salt value of the EncryptionVerifier is the next chunk of data
|
// 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();
|
int checkForSalt = is.readInt();
|
||||||
((InputStream)is).reset();
|
if (is instanceof RecordInputStream) {
|
||||||
|
((RecordInputStream)is).reset();
|
||||||
|
} else {
|
||||||
|
((InputStream)is).reset();
|
||||||
|
}
|
||||||
|
|
||||||
if (checkForSalt == 16) {
|
if (checkForSalt == 16) {
|
||||||
setCspName("");
|
setCspName("");
|
||||||
@ -65,7 +74,9 @@ public class StandardEncryptionHeader extends EncryptionHeader implements Encryp
|
|||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
while (true) {
|
while (true) {
|
||||||
char c = (char) is.readShort();
|
char c = (char) is.readShort();
|
||||||
if (c == 0) break;
|
if (c == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
builder.append(c);
|
builder.append(c);
|
||||||
}
|
}
|
||||||
setCspName(builder.toString());
|
setCspName(builder.toString());
|
||||||
@ -90,6 +101,7 @@ public class StandardEncryptionHeader extends EncryptionHeader implements Encryp
|
|||||||
/**
|
/**
|
||||||
* serializes the header
|
* serializes the header
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void write(LittleEndianByteArrayOutputStream bos) {
|
public void write(LittleEndianByteArrayOutputStream bos) {
|
||||||
int startIdx = bos.getWriteIndex();
|
int startIdx = bos.getWriteIndex();
|
||||||
LittleEndianOutput sizeOutput = bos.createDelayedOutput(LittleEndianConsts.INT_SIZE);
|
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); // reserved1
|
||||||
bos.writeInt(0); // reserved2
|
bos.writeInt(0); // reserved2
|
||||||
String cspName = getCspName();
|
String cspName = getCspName();
|
||||||
if (cspName == null) cspName = getCipherProvider().cipherProviderName;
|
if (cspName == null) {
|
||||||
|
cspName = getCipherProvider().cipherProviderName;
|
||||||
|
}
|
||||||
bos.write(StringUtil.getToUnicodeLE(cspName));
|
bos.write(StringUtil.getToUnicodeLE(cspName));
|
||||||
bos.writeShort(0);
|
bos.writeShort(0);
|
||||||
int headerSize = bos.getWriteIndex()-startIdx-LittleEndianConsts.INT_SIZE;
|
int headerSize = bos.getWriteIndex()-startIdx-LittleEndianConsts.INT_SIZE;
|
||||||
sizeOutput.writeInt(headerSize);
|
sizeOutput.writeInt(headerSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StandardEncryptionHeader clone() throws CloneNotSupportedException {
|
||||||
|
return (StandardEncryptionHeader)super.clone();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,34 +27,29 @@ import org.apache.poi.poifs.crypt.HashAlgorithm;
|
|||||||
import org.apache.poi.util.LittleEndianInput;
|
import org.apache.poi.util.LittleEndianInput;
|
||||||
|
|
||||||
public class StandardEncryptionInfoBuilder implements EncryptionInfoBuilder {
|
public class StandardEncryptionInfoBuilder implements EncryptionInfoBuilder {
|
||||||
|
|
||||||
EncryptionInfo info;
|
|
||||||
StandardEncryptionHeader header;
|
|
||||||
StandardEncryptionVerifier verifier;
|
|
||||||
StandardDecryptor decryptor;
|
|
||||||
StandardEncryptor encryptor;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* initialize the builder from a stream
|
* initialize the builder from a stream
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void initialize(EncryptionInfo info, LittleEndianInput dis) throws IOException {
|
public void initialize(EncryptionInfo info, LittleEndianInput dis) throws IOException {
|
||||||
this.info = info;
|
|
||||||
|
|
||||||
/* int hSize = */ dis.readInt();
|
/* int hSize = */ dis.readInt();
|
||||||
header = new StandardEncryptionHeader(dis);
|
StandardEncryptionHeader header = new StandardEncryptionHeader(dis);
|
||||||
verifier = new StandardEncryptionVerifier(dis, header);
|
info.setHeader(header);
|
||||||
|
info.setVerifier(new StandardEncryptionVerifier(dis, header));
|
||||||
|
|
||||||
if (info.getVersionMinor() == 2 && (info.getVersionMajor() == 3 || info.getVersionMajor() == 4)) {
|
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
|
* initialize the builder from scratch
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void initialize(EncryptionInfo info, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
|
public void initialize(EncryptionInfo info, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
|
||||||
this.info = info;
|
|
||||||
|
|
||||||
if (cipherAlgorithm == null) {
|
if (cipherAlgorithm == null) {
|
||||||
cipherAlgorithm = CipherAlgorithm.aes128;
|
cipherAlgorithm = CipherAlgorithm.aes128;
|
||||||
}
|
}
|
||||||
@ -89,29 +84,13 @@ public class StandardEncryptionInfoBuilder implements EncryptionInfoBuilder {
|
|||||||
if (!found) {
|
if (!found) {
|
||||||
throw new EncryptedDocumentException("KeySize "+keyBits+" not allowed for Cipher "+cipherAlgorithm.toString());
|
throw new EncryptedDocumentException("KeySize "+keyBits+" not allowed for Cipher "+cipherAlgorithm.toString());
|
||||||
}
|
}
|
||||||
header = new StandardEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
|
info.setHeader(new StandardEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
|
||||||
verifier = new StandardEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
|
info.setVerifier(new StandardEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
|
||||||
decryptor = new StandardDecryptor(this);
|
StandardDecryptor dec = new StandardDecryptor();
|
||||||
encryptor = new StandardEncryptor(this);
|
dec.setEncryptionInfo(info);
|
||||||
}
|
info.setDecryptor(dec);
|
||||||
|
StandardEncryptor enc = new StandardEncryptor();
|
||||||
public StandardEncryptionHeader getHeader() {
|
enc.setEncryptionInfo(info);
|
||||||
return header;
|
info.setEncryptor(enc);
|
||||||
}
|
|
||||||
|
|
||||||
public StandardEncryptionVerifier getVerifier() {
|
|
||||||
return verifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public StandardDecryptor getDecryptor() {
|
|
||||||
return decryptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public StandardEncryptor getEncryptor() {
|
|
||||||
return encryptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EncryptionInfo getEncryptionInfo() {
|
|
||||||
return info;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ import org.apache.poi.util.LittleEndianInput;
|
|||||||
/**
|
/**
|
||||||
* Used when checking if a key is valid for a document
|
* 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 static final int SPIN_COUNT = 50000;
|
||||||
private final int verifierHashSize;
|
private final int verifierHashSize;
|
||||||
|
|
||||||
@ -68,6 +68,7 @@ public class StandardEncryptionVerifier extends EncryptionVerifier implements En
|
|||||||
}
|
}
|
||||||
|
|
||||||
// make method visible for this package
|
// make method visible for this package
|
||||||
|
@Override
|
||||||
protected void setSalt(byte salt[]) {
|
protected void setSalt(byte salt[]) {
|
||||||
if (salt == null || salt.length != 16) {
|
if (salt == null || salt.length != 16) {
|
||||||
throw new EncryptedDocumentException("invalid verifier salt");
|
throw new EncryptedDocumentException("invalid verifier salt");
|
||||||
@ -76,15 +77,18 @@ public class StandardEncryptionVerifier extends EncryptionVerifier implements En
|
|||||||
}
|
}
|
||||||
|
|
||||||
// make method visible for this package
|
// make method visible for this package
|
||||||
|
@Override
|
||||||
protected void setEncryptedVerifier(byte encryptedVerifier[]) {
|
protected void setEncryptedVerifier(byte encryptedVerifier[]) {
|
||||||
super.setEncryptedVerifier(encryptedVerifier);
|
super.setEncryptedVerifier(encryptedVerifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
// make method visible for this package
|
// make method visible for this package
|
||||||
|
@Override
|
||||||
protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) {
|
protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) {
|
||||||
super.setEncryptedVerifierHash(encryptedVerifierHash);
|
super.setEncryptedVerifierHash(encryptedVerifierHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void write(LittleEndianByteArrayOutputStream bos) {
|
public void write(LittleEndianByteArrayOutputStream bos) {
|
||||||
// see [MS-OFFCRYPTO] - 2.3.4.9
|
// see [MS-OFFCRYPTO] - 2.3.4.9
|
||||||
byte salt[] = getSalt();
|
byte salt[] = getSalt();
|
||||||
@ -115,4 +119,9 @@ public class StandardEncryptionVerifier extends EncryptionVerifier implements En
|
|||||||
protected int getVerifierHashSize() {
|
protected int getVerifierHashSize() {
|
||||||
return verifierHashSize;
|
return verifierHashSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StandardEncryptionVerifier clone() throws CloneNotSupportedException {
|
||||||
|
return (StandardEncryptionVerifier)super.clone();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,15 +53,13 @@ import org.apache.poi.util.POILogFactory;
|
|||||||
import org.apache.poi.util.POILogger;
|
import org.apache.poi.util.POILogger;
|
||||||
import org.apache.poi.util.TempFile;
|
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 static final POILogger logger = POILogFactory.getLogger(StandardEncryptor.class);
|
||||||
|
|
||||||
private final StandardEncryptionInfoBuilder builder;
|
protected StandardEncryptor() {
|
||||||
|
|
||||||
protected StandardEncryptor(StandardEncryptionInfoBuilder builder) {
|
|
||||||
this.builder = builder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void confirmPassword(String password) {
|
public void confirmPassword(String password) {
|
||||||
// see [MS-OFFCRYPTO] - 2.3.3 EncryptionVerifier
|
// see [MS-OFFCRYPTO] - 2.3.3 EncryptionVerifier
|
||||||
Random r = new SecureRandom();
|
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
|
* 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[]) {
|
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);
|
ver.setSalt(verifierSalt);
|
||||||
SecretKey secretKey = generateSecretKey(password, ver, getKeySizeInBytes());
|
SecretKey secretKey = generateSecretKey(password, ver, getKeySizeInBytes());
|
||||||
@ -111,10 +110,11 @@ public class StandardEncryptor extends Encryptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Cipher getCipher(SecretKey key, String padding) {
|
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);
|
return CryptoFunctions.getCipher(key, ver.getCipherAlgorithm(), ver.getChainingMode(), null, Cipher.ENCRYPT_MODE, padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public OutputStream getDataStream(final DirectoryNode dir)
|
public OutputStream getDataStream(final DirectoryNode dir)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
createEncryptionInfoEntry(dir);
|
createEncryptionInfoEntry(dir);
|
||||||
@ -163,6 +163,7 @@ public class StandardEncryptor extends Encryptor {
|
|||||||
countBytes++;
|
countBytes++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
// the CipherOutputStream adds the padding bytes on close()
|
// the CipherOutputStream adds the padding bytes on close()
|
||||||
super.close();
|
super.close();
|
||||||
@ -175,6 +176,7 @@ public class StandardEncryptor extends Encryptor {
|
|||||||
// TODO: any properties???
|
// TODO: any properties???
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void processPOIFSWriterEvent(POIFSWriterEvent event) {
|
public void processPOIFSWriterEvent(POIFSWriterEvent event) {
|
||||||
try {
|
try {
|
||||||
LittleEndianOutputStream leos = new LittleEndianOutputStream(event.getStream());
|
LittleEndianOutputStream leos = new LittleEndianOutputStream(event.getStream());
|
||||||
@ -200,15 +202,16 @@ public class StandardEncryptor extends Encryptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected int getKeySizeInBytes() {
|
protected int getKeySizeInBytes() {
|
||||||
return builder.getHeader().getKeySize()/8;
|
return getEncryptionInfo().getHeader().getKeySize()/8;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void createEncryptionInfoEntry(DirectoryNode dir) throws IOException {
|
protected void createEncryptionInfoEntry(DirectoryNode dir) throws IOException {
|
||||||
final EncryptionInfo info = builder.getEncryptionInfo();
|
final EncryptionInfo info = getEncryptionInfo();
|
||||||
final StandardEncryptionHeader header = builder.getHeader();
|
final StandardEncryptionHeader header = (StandardEncryptionHeader)info.getHeader();
|
||||||
final StandardEncryptionVerifier verifier = builder.getVerifier();
|
final StandardEncryptionVerifier verifier = (StandardEncryptionVerifier)info.getVerifier();
|
||||||
|
|
||||||
EncryptionRecord er = new EncryptionRecord(){
|
EncryptionRecord er = new EncryptionRecord(){
|
||||||
|
@Override
|
||||||
public void write(LittleEndianByteArrayOutputStream bos) {
|
public void write(LittleEndianByteArrayOutputStream bos) {
|
||||||
bos.writeShort(info.getVersionMajor());
|
bos.writeShort(info.getVersionMajor());
|
||||||
bos.writeShort(info.getVersionMinor());
|
bos.writeShort(info.getVersionMinor());
|
||||||
@ -222,4 +225,9 @@ public class StandardEncryptor extends Encryptor {
|
|||||||
|
|
||||||
// TODO: any properties???
|
// TODO: any properties???
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StandardEncryptor clone() throws CloneNotSupportedException {
|
||||||
|
return (StandardEncryptor)super.clone();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
174
src/java/org/apache/poi/poifs/crypt/xor/XORDecryptor.java
Normal file
174
src/java/org/apache/poi/poifs/crypt/xor/XORDecryptor.java
Normal file
@ -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 <a href="http://msdn.microsoft.com/en-us/library/dd908506.aspx">2.3.7.3 Binary Document XOR Data Transformation Method 1</a>
|
||||||
|
*/
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,30 +1,37 @@
|
|||||||
/* ====================================================================
|
/* ====================================================================
|
||||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
contributor license agreements. See the NOTICE file distributed with
|
contributor license agreements. See the NOTICE file distributed with
|
||||||
this work for additional information regarding copyright ownership.
|
this work for additional information regarding copyright ownership.
|
||||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
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 not use this file except in compliance with
|
||||||
the License. You may obtain a copy of the License at
|
the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
==================================================================== */
|
==================================================================== */
|
||||||
|
|
||||||
package org.apache.poi.hssf.record.crypto;
|
package org.apache.poi.poifs.crypt.xor;
|
||||||
|
|
||||||
|
import org.apache.poi.poifs.crypt.EncryptionHeader;
|
||||||
public interface Biff8Cipher {
|
import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
|
||||||
void startRecord(int currentSid);
|
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
|
||||||
void setNextRecordSize(int recordSize);
|
|
||||||
void skipTwoBytes();
|
public class XOREncryptionHeader extends EncryptionHeader implements EncryptionRecord, Cloneable {
|
||||||
void xor(byte[] buf, int pOffset, int pLen);
|
|
||||||
int xorByte(int rawVal);
|
protected XOREncryptionHeader() {
|
||||||
int xorShort(int rawVal);
|
}
|
||||||
int xorInt(int rawVal);
|
|
||||||
long xorLong(long rawVal);
|
@Override
|
||||||
}
|
public void write(LittleEndianByteArrayOutputStream leos) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XOREncryptionHeader clone() throws CloneNotSupportedException {
|
||||||
|
return (XOREncryptionHeader)super.clone();
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
186
src/java/org/apache/poi/poifs/crypt/xor/XOREncryptor.java
Normal file
186
src/java/org/apache/poi/poifs/crypt/xor/XOREncryptor.java
Normal file
@ -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)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -189,4 +189,9 @@ public class DocumentInputStream extends InputStream implements LittleEndianInpu
|
|||||||
int i = readInt();
|
int i = readInt();
|
||||||
return i & 0xFFFFFFFFL;
|
return i & 0xFFFFFFFFL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readPlain(byte[] buf, int off, int len) {
|
||||||
|
readFully(buf, off, len);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,103 +17,96 @@
|
|||||||
|
|
||||||
package org.apache.poi.util;
|
package org.apache.poi.util;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapts a plain byte array to {@link LittleEndianInput}
|
* Adapts a plain byte array to {@link LittleEndianInput}
|
||||||
*
|
|
||||||
* @author Josh Micich
|
|
||||||
*/
|
*/
|
||||||
public final class LittleEndianByteArrayInputStream implements LittleEndianInput {
|
public final class LittleEndianByteArrayInputStream extends ByteArrayInputStream implements LittleEndianInput {
|
||||||
private final byte[] _buf;
|
|
||||||
private final int _endIndex;
|
|
||||||
private int _readIndex;
|
|
||||||
|
|
||||||
public LittleEndianByteArrayInputStream(byte[] buf, int startOffset, int maxReadLen) { // NOSONAR
|
public LittleEndianByteArrayInputStream(byte[] buf, int startOffset, int maxReadLen) { // NOSONAR
|
||||||
_buf = buf;
|
super(buf, startOffset, maxReadLen);
|
||||||
_readIndex = startOffset;
|
|
||||||
_endIndex = startOffset + maxReadLen;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public LittleEndianByteArrayInputStream(byte[] buf, int startOffset) {
|
public LittleEndianByteArrayInputStream(byte[] buf, int startOffset) {
|
||||||
this(buf, startOffset, buf.length - startOffset);
|
super(buf, startOffset, buf.length - startOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LittleEndianByteArrayInputStream(byte[] buf) {
|
public LittleEndianByteArrayInputStream(byte[] buf) {
|
||||||
this(buf, 0, buf.length);
|
super(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int available() {
|
|
||||||
return _endIndex - _readIndex;
|
|
||||||
}
|
|
||||||
private void checkPosition(int i) {
|
private void checkPosition(int i) {
|
||||||
if (i > _endIndex - _readIndex) {
|
if (i > count - pos) {
|
||||||
throw new RuntimeException("Buffer overrun");
|
throw new RuntimeException("Buffer overrun");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getReadIndex() {
|
public int getReadIndex() {
|
||||||
return _readIndex;
|
return pos;
|
||||||
}
|
}
|
||||||
public byte readByte() {
|
|
||||||
|
@Override
|
||||||
|
public byte readByte() {
|
||||||
checkPosition(1);
|
checkPosition(1);
|
||||||
return _buf[_readIndex++];
|
return (byte)read();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int readInt() {
|
@Override
|
||||||
checkPosition(4);
|
public int readInt() {
|
||||||
int i = _readIndex;
|
final int size = LittleEndianConsts.INT_SIZE;
|
||||||
|
checkPosition(size);
|
||||||
int b0 = _buf[i++] & 0xFF;
|
int le = LittleEndian.getInt(buf, pos);
|
||||||
int b1 = _buf[i++] & 0xFF;
|
super.skip(size);
|
||||||
int b2 = _buf[i++] & 0xFF;
|
return le;
|
||||||
int b3 = _buf[i++] & 0xFF;
|
|
||||||
_readIndex = i;
|
|
||||||
return (b3 << 24) + (b2 << 16) + (b1 << 8) + (b0 << 0);
|
|
||||||
}
|
}
|
||||||
public long readLong() {
|
|
||||||
checkPosition(8);
|
|
||||||
int i = _readIndex;
|
|
||||||
|
|
||||||
int b0 = _buf[i++] & 0xFF;
|
@Override
|
||||||
int b1 = _buf[i++] & 0xFF;
|
public long readLong() {
|
||||||
int b2 = _buf[i++] & 0xFF;
|
final int size = LittleEndianConsts.LONG_SIZE;
|
||||||
int b3 = _buf[i++] & 0xFF;
|
checkPosition(size);
|
||||||
int b4 = _buf[i++] & 0xFF;
|
long le = LittleEndian.getLong(buf, pos);
|
||||||
int b5 = _buf[i++] & 0xFF;
|
super.skip(size);
|
||||||
int b6 = _buf[i++] & 0xFF;
|
return le;
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
public short readShort() {
|
|
||||||
|
@Override
|
||||||
|
public short readShort() {
|
||||||
return (short)readUShort();
|
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;
|
@Override
|
||||||
int b1 = _buf[i++] & 0xFF;
|
public int readUByte() {
|
||||||
_readIndex = i;
|
return readByte() & 0xFF;
|
||||||
return (b1 << 8) + (b0 << 0);
|
|
||||||
}
|
}
|
||||||
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);
|
checkPosition(len);
|
||||||
System.arraycopy(_buf, _readIndex, buf, off, len);
|
read(buffer, off, len);
|
||||||
_readIndex+=len;
|
|
||||||
}
|
}
|
||||||
public void readFully(byte[] buf) {
|
|
||||||
readFully(buf, 0, buf.length);
|
@Override
|
||||||
}
|
public void readFully(byte[] buffer) {
|
||||||
public double readDouble() {
|
checkPosition(buffer.length);
|
||||||
return Double.longBitsToDouble(readLong());
|
read(buffer, 0, buffer.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readPlain(byte[] buf, int off, int len) {
|
||||||
|
readFully(buf, off, len);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,28 +17,26 @@
|
|||||||
|
|
||||||
package org.apache.poi.util;
|
package org.apache.poi.util;
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapts a plain byte array to {@link LittleEndianOutput}
|
* Adapts a plain byte array to {@link LittleEndianOutput}
|
||||||
*
|
|
||||||
*
|
|
||||||
* @author Josh Micich
|
|
||||||
*/
|
*/
|
||||||
public final class LittleEndianByteArrayOutputStream implements LittleEndianOutput, DelayableLittleEndianOutput {
|
public final class LittleEndianByteArrayOutputStream extends OutputStream implements LittleEndianOutput, DelayableLittleEndianOutput {
|
||||||
private final byte[] _buf;
|
private final byte[] _buf;
|
||||||
private final int _endIndex;
|
private final int _endIndex;
|
||||||
private int _writeIndex;
|
private int _writeIndex;
|
||||||
|
|
||||||
public LittleEndianByteArrayOutputStream(byte[] buf, int startOffset, int maxWriteLen) { // NOSONAR
|
public LittleEndianByteArrayOutputStream(byte[] buf, int startOffset, int maxWriteLen) { // NOSONAR
|
||||||
if (startOffset < 0 || startOffset > buf.length) {
|
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 + ")");
|
+ ") is out of allowable range (0.." + buf.length + ")");
|
||||||
}
|
}
|
||||||
_buf = buf;
|
_buf = buf;
|
||||||
_writeIndex = startOffset;
|
_writeIndex = startOffset;
|
||||||
_endIndex = startOffset + maxWriteLen;
|
_endIndex = startOffset + maxWriteLen;
|
||||||
if (_endIndex < startOffset || _endIndex > buf.length) {
|
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 + ")");
|
+ ") 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);
|
checkPosition(1);
|
||||||
_buf[_writeIndex++] = (byte)v;
|
_buf[_writeIndex++] = (byte)v;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeDouble(double v) {
|
@Override
|
||||||
|
public void writeDouble(double v) {
|
||||||
writeLong(Double.doubleToLongBits(v));
|
writeLong(Double.doubleToLongBits(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeInt(int v) {
|
@Override
|
||||||
|
public void writeInt(int v) {
|
||||||
checkPosition(4);
|
checkPosition(4);
|
||||||
int i = _writeIndex;
|
int i = _writeIndex;
|
||||||
_buf[i++] = (byte)((v >>> 0) & 0xFF);
|
_buf[i++] = (byte)((v >>> 0) & 0xFF);
|
||||||
@ -71,33 +72,47 @@ public final class LittleEndianByteArrayOutputStream implements LittleEndianOutp
|
|||||||
_writeIndex = i;
|
_writeIndex = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeLong(long v) {
|
@Override
|
||||||
|
public void writeLong(long v) {
|
||||||
writeInt((int)(v >> 0));
|
writeInt((int)(v >> 0));
|
||||||
writeInt((int)(v >> 32));
|
writeInt((int)(v >> 32));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeShort(int v) {
|
@Override
|
||||||
|
public void writeShort(int v) {
|
||||||
checkPosition(2);
|
checkPosition(2);
|
||||||
int i = _writeIndex;
|
int i = _writeIndex;
|
||||||
_buf[i++] = (byte)((v >>> 0) & 0xFF);
|
_buf[i++] = (byte)((v >>> 0) & 0xFF);
|
||||||
_buf[i++] = (byte)((v >>> 8) & 0xFF);
|
_buf[i++] = (byte)((v >>> 8) & 0xFF);
|
||||||
_writeIndex = i;
|
_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;
|
int len = b.length;
|
||||||
checkPosition(len);
|
checkPosition(len);
|
||||||
System.arraycopy(b, 0, _buf, _writeIndex, len);
|
System.arraycopy(b, 0, _buf, _writeIndex, len);
|
||||||
_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);
|
checkPosition(len);
|
||||||
System.arraycopy(b, offset, _buf, _writeIndex, len);
|
System.arraycopy(b, offset, _buf, _writeIndex, len);
|
||||||
_writeIndex += len;
|
_writeIndex += len;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getWriteIndex() {
|
public int getWriteIndex() {
|
||||||
return _writeIndex;
|
return _writeIndex;
|
||||||
}
|
}
|
||||||
public LittleEndianOutput createDelayedOutput(int size) {
|
|
||||||
|
@Override
|
||||||
|
public LittleEndianOutput createDelayedOutput(int size) {
|
||||||
checkPosition(size);
|
checkPosition(size);
|
||||||
LittleEndianOutput result = new LittleEndianByteArrayOutputStream(_buf, _writeIndex, size);
|
LittleEndianOutput result = new LittleEndianByteArrayOutputStream(_buf, _writeIndex, size);
|
||||||
_writeIndex += size;
|
_writeIndex += size;
|
||||||
|
@ -16,10 +16,7 @@
|
|||||||
==================================================================== */
|
==================================================================== */
|
||||||
|
|
||||||
package org.apache.poi.util;
|
package org.apache.poi.util;
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Josh Micich
|
|
||||||
*/
|
|
||||||
public interface LittleEndianInput {
|
public interface LittleEndianInput {
|
||||||
int available();
|
int available();
|
||||||
byte readByte();
|
byte readByte();
|
||||||
@ -31,4 +28,14 @@ public interface LittleEndianInput {
|
|||||||
double readDouble();
|
double readDouble();
|
||||||
void readFully(byte[] buf);
|
void readFully(byte[] buf);
|
||||||
void readFully(byte[] buf, int off, int len);
|
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);
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,8 @@ public class LittleEndianInputStream extends FilterInputStream implements Little
|
|||||||
super(is);
|
super(is);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int available() {
|
@Override
|
||||||
|
public int available() {
|
||||||
try {
|
try {
|
||||||
return super.available();
|
return super.available();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@ -42,11 +43,13 @@ public class LittleEndianInputStream extends FilterInputStream implements Little
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte readByte() {
|
@Override
|
||||||
|
public byte readByte() {
|
||||||
return (byte)readUByte();
|
return (byte)readUByte();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int readUByte() {
|
@Override
|
||||||
|
public int readUByte() {
|
||||||
byte buf[] = new byte[1];
|
byte buf[] = new byte[1];
|
||||||
try {
|
try {
|
||||||
checkEOF(read(buf), 1);
|
checkEOF(read(buf), 1);
|
||||||
@ -56,11 +59,13 @@ public class LittleEndianInputStream extends FilterInputStream implements Little
|
|||||||
return LittleEndian.getUByte(buf);
|
return LittleEndian.getUByte(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
public double readDouble() {
|
@Override
|
||||||
|
public double readDouble() {
|
||||||
return Double.longBitsToDouble(readLong());
|
return Double.longBitsToDouble(readLong());
|
||||||
}
|
}
|
||||||
|
|
||||||
public int readInt() {
|
@Override
|
||||||
|
public int readInt() {
|
||||||
byte buf[] = new byte[LittleEndianConsts.INT_SIZE];
|
byte buf[] = new byte[LittleEndianConsts.INT_SIZE];
|
||||||
try {
|
try {
|
||||||
checkEOF(read(buf), buf.length);
|
checkEOF(read(buf), buf.length);
|
||||||
@ -82,7 +87,8 @@ public class LittleEndianInputStream extends FilterInputStream implements Little
|
|||||||
return retNum & 0x00FFFFFFFFL;
|
return retNum & 0x00FFFFFFFFL;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long readLong() {
|
@Override
|
||||||
|
public long readLong() {
|
||||||
byte buf[] = new byte[LittleEndianConsts.LONG_SIZE];
|
byte buf[] = new byte[LittleEndianConsts.LONG_SIZE];
|
||||||
try {
|
try {
|
||||||
checkEOF(read(buf), LittleEndianConsts.LONG_SIZE);
|
checkEOF(read(buf), LittleEndianConsts.LONG_SIZE);
|
||||||
@ -92,11 +98,13 @@ public class LittleEndianInputStream extends FilterInputStream implements Little
|
|||||||
return LittleEndian.getLong(buf);
|
return LittleEndian.getLong(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
public short readShort() {
|
@Override
|
||||||
|
public short readShort() {
|
||||||
return (short)readUShort();
|
return (short)readUShort();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int readUShort() {
|
@Override
|
||||||
|
public int readUShort() {
|
||||||
byte buf[] = new byte[LittleEndianConsts.SHORT_SIZE];
|
byte buf[] = new byte[LittleEndianConsts.SHORT_SIZE];
|
||||||
try {
|
try {
|
||||||
checkEOF(read(buf), LittleEndianConsts.SHORT_SIZE);
|
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);
|
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 {
|
try {
|
||||||
checkEOF(read(buf, off, len), len);
|
checkEOF(read(buf, off, len), len);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readPlain(byte[] buf, int off, int len) {
|
||||||
|
readFully(buf, off, len);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ import org.apache.poi.poifs.crypt.CipherAlgorithm;
|
|||||||
import org.apache.poi.poifs.crypt.CryptoFunctions;
|
import org.apache.poi.poifs.crypt.CryptoFunctions;
|
||||||
import org.apache.poi.poifs.crypt.Decryptor;
|
import org.apache.poi.poifs.crypt.Decryptor;
|
||||||
import org.apache.poi.poifs.crypt.EncryptionHeader;
|
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.EncryptionVerifier;
|
||||||
import org.apache.poi.poifs.crypt.HashAlgorithm;
|
import org.apache.poi.poifs.crypt.HashAlgorithm;
|
||||||
import org.apache.poi.poifs.crypt.agile.AgileEncryptionVerifier.AgileCertificateEntry;
|
import org.apache.poi.poifs.crypt.agile.AgileEncryptionVerifier.AgileCertificateEntry;
|
||||||
@ -56,14 +56,14 @@ import org.apache.poi.util.LittleEndian;
|
|||||||
/**
|
/**
|
||||||
* Decryptor implementation for Agile Encryption
|
* Decryptor implementation for Agile Encryption
|
||||||
*/
|
*/
|
||||||
public class AgileDecryptor extends Decryptor {
|
public class AgileDecryptor extends Decryptor implements Cloneable {
|
||||||
private long _length = -1;
|
private long _length = -1;
|
||||||
|
|
||||||
protected static final byte[] kVerifierInputBlock;
|
/* package */ static final byte[] kVerifierInputBlock;
|
||||||
protected static final byte[] kHashedVerifierBlock;
|
/* package */ static final byte[] kHashedVerifierBlock;
|
||||||
protected static final byte[] kCryptoKeyBlock;
|
/* package */ static final byte[] kCryptoKeyBlock;
|
||||||
protected static final byte[] kIntegrityKeyBlock;
|
/* package */ static final byte[] kIntegrityKeyBlock;
|
||||||
protected static final byte[] kIntegrityValueBlock;
|
/* package */ static final byte[] kIntegrityValueBlock;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
kVerifierInputBlock =
|
kVerifierInputBlock =
|
||||||
@ -83,16 +83,16 @@ public class AgileDecryptor extends Decryptor {
|
|||||||
(byte)0xb2, (byte)0x2c, (byte)0x84, (byte)0x33 };
|
(byte)0xb2, (byte)0x2c, (byte)0x84, (byte)0x33 };
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AgileDecryptor(AgileEncryptionInfoBuilder builder) {
|
protected AgileDecryptor() {
|
||||||
super(builder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* set decryption password
|
* set decryption password
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public boolean verifyPassword(String password) throws GeneralSecurityException {
|
public boolean verifyPassword(String password) throws GeneralSecurityException {
|
||||||
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)builder.getVerifier();
|
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
|
||||||
AgileEncryptionHeader header = (AgileEncryptionHeader)builder.getHeader();
|
AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
|
||||||
HashAlgorithm hashAlgo = header.getHashAlgorithmEx();
|
HashAlgorithm hashAlgo = header.getHashAlgorithmEx();
|
||||||
CipherAlgorithm cipherAlgo = header.getCipherAlgorithm();
|
CipherAlgorithm cipherAlgo = header.getCipherAlgorithm();
|
||||||
int blockSize = header.getBlockSize();
|
int blockSize = header.getBlockSize();
|
||||||
@ -113,7 +113,7 @@ public class AgileDecryptor extends Decryptor {
|
|||||||
* blockSize bytes.
|
* blockSize bytes.
|
||||||
* 4. Use base64 to encode the result of step 3.
|
* 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);
|
setVerifier(verfierInputEnc);
|
||||||
MessageDigest hashMD = getMessageDigest(hashAlgo);
|
MessageDigest hashMD = getMessageDigest(hashAlgo);
|
||||||
byte[] verifierHash = hashMD.digest(verfierInputEnc);
|
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.
|
* 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.
|
* 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);
|
verifierHashDec = getBlock0(verifierHashDec, hashAlgo.hashSize);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -146,7 +146,7 @@ public class AgileDecryptor extends Decryptor {
|
|||||||
* blockSize bytes.
|
* blockSize bytes.
|
||||||
* 4. Use base64 to encode the result of step 3.
|
* 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);
|
keyspec = getBlock0(keyspec, keySize);
|
||||||
SecretKeySpec secretKey = new SecretKeySpec(keyspec, ver.getCipherAlgorithm().jceId);
|
SecretKeySpec secretKey = new SecretKeySpec(keyspec, ver.getCipherAlgorithm().jceId);
|
||||||
|
|
||||||
@ -204,8 +204,8 @@ public class AgileDecryptor extends Decryptor {
|
|||||||
* @throws GeneralSecurityException
|
* @throws GeneralSecurityException
|
||||||
*/
|
*/
|
||||||
public boolean verifyPassword(KeyPair keyPair, X509Certificate x509) throws GeneralSecurityException {
|
public boolean verifyPassword(KeyPair keyPair, X509Certificate x509) throws GeneralSecurityException {
|
||||||
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)builder.getVerifier();
|
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
|
||||||
AgileEncryptionHeader header = (AgileEncryptionHeader)builder.getHeader();
|
AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
|
||||||
HashAlgorithm hashAlgo = header.getHashAlgorithmEx();
|
HashAlgorithm hashAlgo = header.getHashAlgorithmEx();
|
||||||
CipherAlgorithm cipherAlgo = header.getCipherAlgorithm();
|
CipherAlgorithm cipherAlgo = header.getCipherAlgorithm();
|
||||||
int blockSize = header.getBlockSize();
|
int blockSize = header.getBlockSize();
|
||||||
@ -217,7 +217,9 @@ public class AgileDecryptor extends Decryptor {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ace == null) return false;
|
if (ace == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Cipher cipher = Cipher.getInstance("RSA");
|
Cipher cipher = Cipher.getInstance("RSA");
|
||||||
cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
|
cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
|
||||||
@ -255,9 +257,9 @@ public class AgileDecryptor extends Decryptor {
|
|||||||
return fillSize;
|
return fillSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static byte[] hashInput(EncryptionInfoBuilder builder, byte pwHash[], byte blockKey[], byte inputKey[], int cipherMode) {
|
protected static byte[] hashInput(EncryptionInfo encryptionInfo, byte pwHash[], byte blockKey[], byte inputKey[], int cipherMode) {
|
||||||
EncryptionVerifier ver = builder.getVerifier();
|
EncryptionVerifier ver = encryptionInfo.getVerifier();
|
||||||
AgileDecryptor dec = (AgileDecryptor)builder.getDecryptor();
|
AgileDecryptor dec = (AgileDecryptor)encryptionInfo.getDecryptor();
|
||||||
int keySize = dec.getKeySizeInBytes();
|
int keySize = dec.getKeySizeInBytes();
|
||||||
int blockSize = dec.getBlockSizeInBytes();
|
int blockSize = dec.getBlockSizeInBytes();
|
||||||
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
|
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
|
||||||
@ -278,6 +280,7 @@ public class AgileDecryptor extends Decryptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
@SuppressWarnings("resource")
|
@SuppressWarnings("resource")
|
||||||
public InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException {
|
public InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException {
|
||||||
DocumentInputStream dis = dir.createDocumentInputStream(DEFAULT_POIFS_ENTRY);
|
DocumentInputStream dis = dir.createDocumentInputStream(DEFAULT_POIFS_ENTRY);
|
||||||
@ -285,17 +288,20 @@ public class AgileDecryptor extends Decryptor {
|
|||||||
return new AgileCipherInputStream(dis, _length);
|
return new AgileCipherInputStream(dis, _length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public long getLength(){
|
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;
|
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 {
|
throws GeneralSecurityException {
|
||||||
EncryptionHeader header = builder.getHeader();
|
EncryptionHeader header = encryptionInfo.getHeader();
|
||||||
if (existing == null || lastChunk) {
|
String padding = (lastChunk ? "PKCS5Padding" : "NoPadding");
|
||||||
String padding = (lastChunk ? "PKCS5Padding" : "NoPadding");
|
if (existing == null || !existing.getAlgorithm().endsWith(padding)) {
|
||||||
existing = getCipher(skey, header.getCipherAlgorithm(), header.getChainingMode(), header.getKeySalt(), encryptionMode, 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
|
// TODO: calculate integrity hmac while reading the stream
|
||||||
// for a post-validation of the data
|
// for a post-validation of the data
|
||||||
|
|
||||||
|
@Override
|
||||||
protected Cipher initCipherForBlock(Cipher cipher, int block)
|
protected Cipher initCipherForBlock(Cipher cipher, int block)
|
||||||
throws GeneralSecurityException {
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.EncryptionDocument;
|
||||||
import com.microsoft.schemas.office.x2006.encryption.STCipherChaining;
|
import com.microsoft.schemas.office.x2006.encryption.STCipherChaining;
|
||||||
|
|
||||||
public class AgileEncryptionHeader extends EncryptionHeader {
|
public class AgileEncryptionHeader extends EncryptionHeader implements Cloneable {
|
||||||
private byte encryptedHmacKey[], encryptedHmacValue[];
|
private byte encryptedHmacKey[], encryptedHmacValue[];
|
||||||
|
|
||||||
public AgileEncryptionHeader(String descriptor) {
|
public AgileEncryptionHeader(String descriptor) {
|
||||||
@ -99,6 +99,7 @@ public class AgileEncryptionHeader extends EncryptionHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// make method visible for this package
|
// make method visible for this package
|
||||||
|
@Override
|
||||||
protected void setKeySalt(byte salt[]) {
|
protected void setKeySalt(byte salt[]) {
|
||||||
if (salt == null || salt.length != getBlockSize()) {
|
if (salt == null || salt.length != getBlockSize()) {
|
||||||
throw new EncryptedDocumentException("invalid verifier salt");
|
throw new EncryptedDocumentException("invalid verifier salt");
|
||||||
@ -121,4 +122,13 @@ public class AgileEncryptionHeader extends EncryptionHeader {
|
|||||||
protected void setEncryptedHmacValue(byte[] encryptedHmacValue) {
|
protected void setEncryptedHmacValue(byte[] encryptedHmacValue) {
|
||||||
this.encryptedHmacValue = (encryptedHmacValue == null) ? null : encryptedHmacValue.clone();
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -35,30 +35,24 @@ import com.microsoft.schemas.office.x2006.encryption.EncryptionDocument;
|
|||||||
|
|
||||||
public class AgileEncryptionInfoBuilder implements EncryptionInfoBuilder {
|
public class AgileEncryptionInfoBuilder implements EncryptionInfoBuilder {
|
||||||
|
|
||||||
EncryptionInfo info;
|
|
||||||
AgileEncryptionHeader header;
|
|
||||||
AgileEncryptionVerifier verifier;
|
|
||||||
AgileDecryptor decryptor;
|
|
||||||
AgileEncryptor encryptor;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(EncryptionInfo ei, LittleEndianInput dis) throws IOException {
|
public void initialize(EncryptionInfo info, LittleEndianInput dis) throws IOException {
|
||||||
this.info = ei;
|
|
||||||
|
|
||||||
EncryptionDocument ed = parseDescriptor((InputStream)dis);
|
EncryptionDocument ed = parseDescriptor((InputStream)dis);
|
||||||
header = new AgileEncryptionHeader(ed);
|
info.setHeader(new AgileEncryptionHeader(ed));
|
||||||
verifier = new AgileEncryptionVerifier(ed);
|
info.setVerifier(new AgileEncryptionVerifier(ed));
|
||||||
if (ei.getVersionMajor() == EncryptionMode.agile.versionMajor
|
if (info.getVersionMajor() == EncryptionMode.agile.versionMajor
|
||||||
&& ei.getVersionMinor() == EncryptionMode.agile.versionMinor) {
|
&& info.getVersionMinor() == EncryptionMode.agile.versionMinor) {
|
||||||
decryptor = new AgileDecryptor(this);
|
AgileDecryptor dec = new AgileDecryptor();
|
||||||
encryptor = new AgileEncryptor(this);
|
dec.setEncryptionInfo(info);
|
||||||
|
info.setDecryptor(dec);
|
||||||
|
AgileEncryptor enc = new AgileEncryptor();
|
||||||
|
enc.setEncryptionInfo(info);
|
||||||
|
info.setEncryptor(enc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(EncryptionInfo ei, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
|
public void initialize(EncryptionInfo info, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
|
||||||
this.info = ei;
|
|
||||||
|
|
||||||
if (cipherAlgorithm == null) {
|
if (cipherAlgorithm == null) {
|
||||||
cipherAlgorithm = CipherAlgorithm.aes128;
|
cipherAlgorithm = CipherAlgorithm.aes128;
|
||||||
}
|
}
|
||||||
@ -87,30 +81,14 @@ public class AgileEncryptionInfoBuilder implements EncryptionInfoBuilder {
|
|||||||
if (!found) {
|
if (!found) {
|
||||||
throw new EncryptedDocumentException("KeySize "+keyBits+" not allowed for Cipher "+cipherAlgorithm.toString());
|
throw new EncryptedDocumentException("KeySize "+keyBits+" not allowed for Cipher "+cipherAlgorithm.toString());
|
||||||
}
|
}
|
||||||
header = new AgileEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
|
info.setHeader(new AgileEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
|
||||||
verifier = new AgileEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
|
info.setVerifier(new AgileEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
|
||||||
decryptor = new AgileDecryptor(this);
|
AgileDecryptor dec = new AgileDecryptor();
|
||||||
encryptor = new AgileEncryptor(this);
|
dec.setEncryptionInfo(info);
|
||||||
}
|
info.setDecryptor(dec);
|
||||||
|
AgileEncryptor enc = new AgileEncryptor();
|
||||||
public AgileEncryptionHeader getHeader() {
|
enc.setEncryptionInfo(info);
|
||||||
return header;
|
info.setEncryptor(enc);
|
||||||
}
|
|
||||||
|
|
||||||
public AgileEncryptionVerifier getVerifier() {
|
|
||||||
return verifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AgileDecryptor getDecryptor() {
|
|
||||||
return decryptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AgileEncryptor getEncryptor() {
|
|
||||||
return encryptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected EncryptionInfo getInfo() {
|
|
||||||
return info;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static EncryptionDocument parseDescriptor(String descriptor) {
|
protected static EncryptionDocument parseDescriptor(String descriptor) {
|
||||||
|
@ -39,7 +39,7 @@ import com.microsoft.schemas.office.x2006.keyEncryptor.password.CTPasswordKeyEnc
|
|||||||
/**
|
/**
|
||||||
* Used when checking if a key is valid for a document
|
* 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 {
|
public static class AgileCertificateEntry {
|
||||||
X509Certificate x509;
|
X509Certificate x509;
|
||||||
@ -87,8 +87,9 @@ public class AgileEncryptionVerifier extends EncryptionVerifier {
|
|||||||
setEncryptedVerifierHash(keyData.getEncryptedVerifierHashValue());
|
setEncryptedVerifierHash(keyData.getEncryptedVerifierHashValue());
|
||||||
|
|
||||||
int saltSize = keyData.getSaltSize();
|
int saltSize = keyData.getSaltSize();
|
||||||
if (saltSize != getSalt().length)
|
if (saltSize != getSalt().length) {
|
||||||
throw new EncryptedDocumentException("Invalid salt size");
|
throw new EncryptedDocumentException("Invalid salt size");
|
||||||
|
}
|
||||||
|
|
||||||
switch (keyData.getCipherChaining().intValue()) {
|
switch (keyData.getCipherChaining().intValue()) {
|
||||||
case STCipherChaining.INT_CHAINING_MODE_CBC:
|
case STCipherChaining.INT_CHAINING_MODE_CBC:
|
||||||
@ -101,7 +102,9 @@ public class AgileEncryptionVerifier extends EncryptionVerifier {
|
|||||||
throw new EncryptedDocumentException("Unsupported chaining mode - "+keyData.getCipherChaining().toString());
|
throw new EncryptedDocumentException("Unsupported chaining mode - "+keyData.getCipherChaining().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!encList.hasNext()) return;
|
if (!encList.hasNext()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||||
@ -125,6 +128,7 @@ public class AgileEncryptionVerifier extends EncryptionVerifier {
|
|||||||
setSpinCount(100000); // TODO: use parameter
|
setSpinCount(100000); // TODO: use parameter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected void setSalt(byte salt[]) {
|
protected void setSalt(byte salt[]) {
|
||||||
if (salt == null || salt.length != getCipherAlgorithm().blockSize) {
|
if (salt == null || salt.length != getCipherAlgorithm().blockSize) {
|
||||||
throw new EncryptedDocumentException("invalid verifier salt");
|
throw new EncryptedDocumentException("invalid verifier salt");
|
||||||
@ -133,16 +137,19 @@ public class AgileEncryptionVerifier extends EncryptionVerifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// make method visible for this package
|
// make method visible for this package
|
||||||
|
@Override
|
||||||
protected void setEncryptedVerifier(byte encryptedVerifier[]) {
|
protected void setEncryptedVerifier(byte encryptedVerifier[]) {
|
||||||
super.setEncryptedVerifier(encryptedVerifier);
|
super.setEncryptedVerifier(encryptedVerifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
// make method visible for this package
|
// make method visible for this package
|
||||||
|
@Override
|
||||||
protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) {
|
protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) {
|
||||||
super.setEncryptedVerifierHash(encryptedVerifierHash);
|
super.setEncryptedVerifierHash(encryptedVerifierHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
// make method visible for this package
|
// make method visible for this package
|
||||||
|
@Override
|
||||||
protected void setEncryptedKey(byte[] encryptedKey) {
|
protected void setEncryptedKey(byte[] encryptedKey) {
|
||||||
super.setEncryptedKey(encryptedKey);
|
super.setEncryptedKey(encryptedKey);
|
||||||
}
|
}
|
||||||
@ -156,4 +163,12 @@ public class AgileEncryptionVerifier extends EncryptionVerifier {
|
|||||||
public List<AgileCertificateEntry> getCertificates() {
|
public List<AgileCertificateEntry> getCertificates() {
|
||||||
return certList;
|
return certList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AgileEncryptionVerifier clone() throws CloneNotSupportedException {
|
||||||
|
AgileEncryptionVerifier other = (AgileEncryptionVerifier)super.clone();
|
||||||
|
// TODO: deep copy of certList
|
||||||
|
other.certList = new ArrayList<AgileCertificateEntry>(certList);
|
||||||
|
return other;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,7 @@ import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
|
|||||||
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
||||||
import org.apache.poi.util.LittleEndian;
|
import org.apache.poi.util.LittleEndian;
|
||||||
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
|
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
|
||||||
|
import org.apache.poi.util.LittleEndianConsts;
|
||||||
import org.apache.xmlbeans.XmlOptions;
|
import org.apache.xmlbeans.XmlOptions;
|
||||||
|
|
||||||
import com.microsoft.schemas.office.x2006.encryption.CTDataIntegrity;
|
import com.microsoft.schemas.office.x2006.encryption.CTDataIntegrity;
|
||||||
@ -74,21 +75,20 @@ import com.microsoft.schemas.office.x2006.encryption.STHashAlgorithm;
|
|||||||
import com.microsoft.schemas.office.x2006.keyEncryptor.certificate.CTCertificateKeyEncryptor;
|
import com.microsoft.schemas.office.x2006.keyEncryptor.certificate.CTCertificateKeyEncryptor;
|
||||||
import com.microsoft.schemas.office.x2006.keyEncryptor.password.CTPasswordKeyEncryptor;
|
import com.microsoft.schemas.office.x2006.keyEncryptor.password.CTPasswordKeyEncryptor;
|
||||||
|
|
||||||
public class AgileEncryptor extends Encryptor {
|
public class AgileEncryptor extends Encryptor implements Cloneable {
|
||||||
private final AgileEncryptionInfoBuilder builder;
|
|
||||||
private byte integritySalt[];
|
private byte integritySalt[];
|
||||||
private byte pwHash[];
|
private byte pwHash[];
|
||||||
|
|
||||||
protected AgileEncryptor(AgileEncryptionInfoBuilder builder) {
|
protected AgileEncryptor() {
|
||||||
this.builder = builder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void confirmPassword(String password) {
|
public void confirmPassword(String password) {
|
||||||
// see [MS-OFFCRYPTO] - 2.3.3 EncryptionVerifier
|
// see [MS-OFFCRYPTO] - 2.3.3 EncryptionVerifier
|
||||||
Random r = new SecureRandom();
|
Random r = new SecureRandom();
|
||||||
int blockSize = builder.getHeader().getBlockSize();
|
int blockSize = getEncryptionInfo().getHeader().getBlockSize();
|
||||||
int keySize = builder.getHeader().getKeySize()/8;
|
int keySize = getEncryptionInfo().getHeader().getKeySize()/8;
|
||||||
int hashSize = builder.getHeader().getHashAlgorithmEx().hashSize;
|
int hashSize = getEncryptionInfo().getHeader().getHashAlgorithmEx().hashSize;
|
||||||
|
|
||||||
byte[] newVerifierSalt = new byte[blockSize]
|
byte[] newVerifierSalt = new byte[blockSize]
|
||||||
, newVerifier = new byte[blockSize]
|
, newVerifier = new byte[blockSize]
|
||||||
@ -104,10 +104,11 @@ public class AgileEncryptor extends Encryptor {
|
|||||||
confirmPassword(password, newKeySpec, newKeySalt, newVerifierSalt, newVerifier, newIntegritySalt);
|
confirmPassword(password, newKeySpec, newKeySalt, newVerifierSalt, newVerifier, newIntegritySalt);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void confirmPassword(String password, byte keySpec[], byte keySalt[], byte verifier[], byte verifierSalt[], byte integritySalt[]) {
|
@Override
|
||||||
AgileEncryptionVerifier ver = builder.getVerifier();
|
public void confirmPassword(String password, byte keySpec[], byte keySalt[], byte verifier[], byte verifierSalt[], byte integritySalt[]) {
|
||||||
|
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
|
||||||
ver.setSalt(verifierSalt);
|
ver.setSalt(verifierSalt);
|
||||||
AgileEncryptionHeader header = builder.getHeader();
|
AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
|
||||||
header.setKeySalt(keySalt);
|
header.setKeySalt(keySalt);
|
||||||
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
|
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
|
||||||
|
|
||||||
@ -128,7 +129,7 @@ public class AgileEncryptor extends Encryptor {
|
|||||||
* blockSize bytes.
|
* blockSize bytes.
|
||||||
* 4. Use base64 to encode the result of step 3.
|
* 4. Use base64 to encode the result of step 3.
|
||||||
*/
|
*/
|
||||||
byte encryptedVerifier[] = hashInput(builder, pwHash, kVerifierInputBlock, verifier, Cipher.ENCRYPT_MODE);
|
byte encryptedVerifier[] = hashInput(getEncryptionInfo(), pwHash, kVerifierInputBlock, verifier, Cipher.ENCRYPT_MODE);
|
||||||
ver.setEncryptedVerifier(encryptedVerifier);
|
ver.setEncryptedVerifier(encryptedVerifier);
|
||||||
|
|
||||||
|
|
||||||
@ -146,7 +147,7 @@ public class AgileEncryptor extends Encryptor {
|
|||||||
*/
|
*/
|
||||||
MessageDigest hashMD = getMessageDigest(hashAlgo);
|
MessageDigest hashMD = getMessageDigest(hashAlgo);
|
||||||
byte[] hashedVerifier = hashMD.digest(verifier);
|
byte[] hashedVerifier = hashMD.digest(verifier);
|
||||||
byte encryptedVerifierHash[] = hashInput(builder, pwHash, kHashedVerifierBlock, hashedVerifier, Cipher.ENCRYPT_MODE);
|
byte encryptedVerifierHash[] = hashInput(getEncryptionInfo(), pwHash, kHashedVerifierBlock, hashedVerifier, Cipher.ENCRYPT_MODE);
|
||||||
ver.setEncryptedVerifierHash(encryptedVerifierHash);
|
ver.setEncryptedVerifierHash(encryptedVerifierHash);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -162,7 +163,7 @@ public class AgileEncryptor extends Encryptor {
|
|||||||
* blockSize bytes.
|
* blockSize bytes.
|
||||||
* 4. Use base64 to encode the result of step 3.
|
* 4. Use base64 to encode the result of step 3.
|
||||||
*/
|
*/
|
||||||
byte encryptedKey[] = hashInput(builder, pwHash, kCryptoKeyBlock, keySpec, Cipher.ENCRYPT_MODE);
|
byte encryptedKey[] = hashInput(getEncryptionInfo(), pwHash, kCryptoKeyBlock, keySpec, Cipher.ENCRYPT_MODE);
|
||||||
ver.setEncryptedKey(encryptedKey);
|
ver.setEncryptedKey(encryptedKey);
|
||||||
|
|
||||||
SecretKey secretKey = new SecretKeySpec(keySpec, ver.getCipherAlgorithm().jceId);
|
SecretKey secretKey = new SecretKeySpec(keySpec, ver.getCipherAlgorithm().jceId);
|
||||||
@ -214,6 +215,7 @@ public class AgileEncryptor extends Encryptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public OutputStream getDataStream(DirectoryNode dir)
|
public OutputStream getDataStream(DirectoryNode dir)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
// TODO: initialize headers
|
// TODO: initialize headers
|
||||||
@ -234,14 +236,14 @@ public class AgileEncryptor extends Encryptor {
|
|||||||
// as the integrity hmac needs to contain the StreamSize,
|
// as the integrity hmac needs to contain the StreamSize,
|
||||||
// it's not possible to calculate it on-the-fly while buffering
|
// it's not possible to calculate it on-the-fly while buffering
|
||||||
// TODO: add stream size parameter to getDataStream()
|
// TODO: add stream size parameter to getDataStream()
|
||||||
AgileEncryptionVerifier ver = builder.getVerifier();
|
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
|
||||||
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
|
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
|
||||||
Mac integrityMD = CryptoFunctions.getMac(hashAlgo);
|
Mac integrityMD = CryptoFunctions.getMac(hashAlgo);
|
||||||
integrityMD.init(new SecretKeySpec(integritySalt, hashAlgo.jceHmacId));
|
integrityMD.init(new SecretKeySpec(integritySalt, hashAlgo.jceHmacId));
|
||||||
|
|
||||||
byte buf[] = new byte[1024];
|
byte buf[] = new byte[1024];
|
||||||
LittleEndian.putLong(buf, 0, oleStreamSize);
|
LittleEndian.putLong(buf, 0, oleStreamSize);
|
||||||
integrityMD.update(buf, 0, LittleEndian.LONG_SIZE);
|
integrityMD.update(buf, 0, LittleEndianConsts.LONG_SIZE);
|
||||||
|
|
||||||
InputStream fis = new FileInputStream(tmpFile);
|
InputStream fis = new FileInputStream(tmpFile);
|
||||||
try {
|
try {
|
||||||
@ -255,7 +257,7 @@ public class AgileEncryptor extends Encryptor {
|
|||||||
|
|
||||||
byte hmacValue[] = integrityMD.doFinal();
|
byte hmacValue[] = integrityMD.doFinal();
|
||||||
|
|
||||||
AgileEncryptionHeader header = builder.getHeader();
|
AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
|
||||||
int blockSize = header.getBlockSize();
|
int blockSize = header.getBlockSize();
|
||||||
byte iv[] = CryptoFunctions.generateIv(header.getHashAlgorithmEx(), header.getKeySalt(), kIntegrityValueBlock, blockSize);
|
byte iv[] = CryptoFunctions.generateIv(header.getHashAlgorithmEx(), header.getKeySalt(), kIntegrityValueBlock, blockSize);
|
||||||
Cipher cipher = CryptoFunctions.getCipher(getSecretKey(), header.getCipherAlgorithm(), header.getChainingMode(), iv, Cipher.ENCRYPT_MODE);
|
Cipher cipher = CryptoFunctions.getCipher(getSecretKey(), header.getCipherAlgorithm(), header.getChainingMode(), iv, Cipher.ENCRYPT_MODE);
|
||||||
@ -271,8 +273,8 @@ public class AgileEncryptor extends Encryptor {
|
|||||||
CTKeyEncryptor.Uri.HTTP_SCHEMAS_MICROSOFT_COM_OFFICE_2006_KEY_ENCRYPTOR_CERTIFICATE;
|
CTKeyEncryptor.Uri.HTTP_SCHEMAS_MICROSOFT_COM_OFFICE_2006_KEY_ENCRYPTOR_CERTIFICATE;
|
||||||
|
|
||||||
protected EncryptionDocument createEncryptionDocument() {
|
protected EncryptionDocument createEncryptionDocument() {
|
||||||
AgileEncryptionVerifier ver = builder.getVerifier();
|
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
|
||||||
AgileEncryptionHeader header = builder.getHeader();
|
AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
|
||||||
|
|
||||||
EncryptionDocument ed = EncryptionDocument.Factory.newInstance();
|
EncryptionDocument ed = EncryptionDocument.Factory.newInstance();
|
||||||
CTEncryption edRoot = ed.addNewEncryption();
|
CTEncryption edRoot = ed.addNewEncryption();
|
||||||
@ -379,9 +381,10 @@ public class AgileEncryptor extends Encryptor {
|
|||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
DataSpaceMapUtils.addDefaultDataSpace(dir);
|
DataSpaceMapUtils.addDefaultDataSpace(dir);
|
||||||
|
|
||||||
final EncryptionInfo info = builder.getInfo();
|
final EncryptionInfo info = getEncryptionInfo();
|
||||||
|
|
||||||
EncryptionRecord er = new EncryptionRecord(){
|
EncryptionRecord er = new EncryptionRecord(){
|
||||||
|
@Override
|
||||||
public void write(LittleEndianByteArrayOutputStream bos) {
|
public void write(LittleEndianByteArrayOutputStream bos) {
|
||||||
// EncryptionVersionInfo (4 bytes): A Version structure (section 2.1.4), where
|
// EncryptionVersionInfo (4 bytes): A Version structure (section 2.1.4), where
|
||||||
// Version.vMajor MUST be 0x0004 and Version.vMinor MUST be 0x0004
|
// Version.vMajor MUST be 0x0004 and Version.vMinor MUST be 0x0004
|
||||||
@ -422,7 +425,7 @@ public class AgileEncryptor extends Encryptor {
|
|||||||
@Override
|
@Override
|
||||||
protected Cipher initCipherForBlock(Cipher existing, int block, boolean lastChunk)
|
protected Cipher initCipherForBlock(Cipher existing, int block, boolean lastChunk)
|
||||||
throws GeneralSecurityException {
|
throws GeneralSecurityException {
|
||||||
return AgileDecryptor.initCipherForBlock(existing, block, lastChunk, builder, getSecretKey(), Cipher.ENCRYPT_MODE);
|
return AgileDecryptor.initCipherForBlock(existing, block, lastChunk, getEncryptionInfo(), getSecretKey(), Cipher.ENCRYPT_MODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -439,4 +442,11 @@ public class AgileEncryptor extends Encryptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AgileEncryptor clone() throws CloneNotSupportedException {
|
||||||
|
AgileEncryptor other = (AgileEncryptor)super.clone();
|
||||||
|
other.integritySalt = (integritySalt == null) ? null : integritySalt.clone();
|
||||||
|
other.pwHash = (pwHash == null) ? null : pwHash.clone();
|
||||||
|
return other;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,7 @@
|
|||||||
==================================================================== */
|
==================================================================== */
|
||||||
package org.apache.poi.poifs.crypt;
|
package org.apache.poi.poifs.crypt;
|
||||||
|
|
||||||
import static org.hamcrest.core.IsEqual.equalTo;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
@ -94,6 +93,7 @@ public class TestAgileEncryptionParameters {
|
|||||||
os.close();
|
os.close();
|
||||||
bos.reset();
|
bos.reset();
|
||||||
fsEnc.writeFilesystem(bos);
|
fsEnc.writeFilesystem(bos);
|
||||||
|
fsEnc.close();
|
||||||
|
|
||||||
POIFSFileSystem fsDec = new POIFSFileSystem(new ByteArrayInputStream(bos.toByteArray()));
|
POIFSFileSystem fsDec = new POIFSFileSystem(new ByteArrayInputStream(bos.toByteArray()));
|
||||||
EncryptionInfo infoDec = new EncryptionInfo(fsDec);
|
EncryptionInfo infoDec = new EncryptionInfo(fsDec);
|
||||||
@ -103,6 +103,7 @@ public class TestAgileEncryptionParameters {
|
|||||||
InputStream is = dec.getDataStream(fsDec);
|
InputStream is = dec.getDataStream(fsDec);
|
||||||
byte actualData[] = IOUtils.toByteArray(is);
|
byte actualData[] = IOUtils.toByteArray(is);
|
||||||
is.close();
|
is.close();
|
||||||
assertThat("Failed roundtrip - "+ca+"-"+ha+"-"+cm, testData, equalTo(actualData));
|
fsDec.close();
|
||||||
|
assertArrayEquals("Failed roundtrip - "+ca+"-"+ha+"-"+cm, testData, actualData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,8 @@ public final class DocumentEncryptionAtom extends PositionDependentRecordAtom {
|
|||||||
|
|
||||||
ByteArrayInputStream bis = new ByteArrayInputStream(source, start+8, len-8);
|
ByteArrayInputStream bis = new ByteArrayInputStream(source, start+8, len-8);
|
||||||
LittleEndianInputStream leis = new LittleEndianInputStream(bis);
|
LittleEndianInputStream leis = new LittleEndianInputStream(bis);
|
||||||
ei = new EncryptionInfo(leis, true);
|
ei = new EncryptionInfo(leis, EncryptionMode.cryptoAPI);
|
||||||
|
leis.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public DocumentEncryptionAtom() {
|
public DocumentEncryptionAtom() {
|
||||||
@ -121,6 +122,7 @@ public final class DocumentEncryptionAtom extends PositionDependentRecordAtom {
|
|||||||
LittleEndian.putInt(_header, 4, bos.getWriteIndex());
|
LittleEndian.putInt(_header, 4, bos.getWriteIndex());
|
||||||
out.write(_header);
|
out.write(_header);
|
||||||
out.write(data, 0, bos.getWriteIndex());
|
out.write(data, 0, bos.getWriteIndex());
|
||||||
|
bos.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
package org.apache.poi.hslf.usermodel;
|
package org.apache.poi.hslf.usermodel;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -25,9 +27,6 @@ import java.util.Map;
|
|||||||
import java.util.NavigableMap;
|
import java.util.NavigableMap;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.CipherOutputStream;
|
|
||||||
|
|
||||||
import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException;
|
import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException;
|
||||||
import org.apache.poi.hslf.exceptions.EncryptedPowerPointFileException;
|
import org.apache.poi.hslf.exceptions.EncryptedPowerPointFileException;
|
||||||
import org.apache.poi.hslf.record.DocumentEncryptionAtom;
|
import org.apache.poi.hslf.record.DocumentEncryptionAtom;
|
||||||
@ -36,26 +35,45 @@ import org.apache.poi.hslf.record.PositionDependentRecord;
|
|||||||
import org.apache.poi.hslf.record.Record;
|
import org.apache.poi.hslf.record.Record;
|
||||||
import org.apache.poi.hslf.record.UserEditAtom;
|
import org.apache.poi.hslf.record.UserEditAtom;
|
||||||
import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
|
import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
|
||||||
|
import org.apache.poi.poifs.crypt.ChunkedCipherInputStream;
|
||||||
|
import org.apache.poi.poifs.crypt.ChunkedCipherOutputStream;
|
||||||
import org.apache.poi.poifs.crypt.Decryptor;
|
import org.apache.poi.poifs.crypt.Decryptor;
|
||||||
import org.apache.poi.poifs.crypt.EncryptionInfo;
|
import org.apache.poi.poifs.crypt.EncryptionInfo;
|
||||||
import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIDecryptor;
|
import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIDecryptor;
|
||||||
import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptor;
|
import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptor;
|
||||||
import org.apache.poi.util.BitField;
|
import org.apache.poi.util.BitField;
|
||||||
|
import org.apache.poi.util.IOUtils;
|
||||||
import org.apache.poi.util.Internal;
|
import org.apache.poi.util.Internal;
|
||||||
import org.apache.poi.util.LittleEndian;
|
import org.apache.poi.util.LittleEndian;
|
||||||
|
import org.apache.poi.util.LittleEndianByteArrayInputStream;
|
||||||
|
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class provides helper functions for encrypted PowerPoint documents.
|
* This class provides helper functions for encrypted PowerPoint documents.
|
||||||
*/
|
*/
|
||||||
@Internal
|
@Internal
|
||||||
public class HSLFSlideShowEncrypted {
|
public class HSLFSlideShowEncrypted implements Closeable {
|
||||||
DocumentEncryptionAtom dea;
|
DocumentEncryptionAtom dea;
|
||||||
CryptoAPIEncryptor enc = null;
|
CryptoAPIEncryptor enc = null;
|
||||||
CryptoAPIDecryptor dec = null;
|
CryptoAPIDecryptor dec = null;
|
||||||
Cipher cipher = null;
|
// Cipher cipher = null;
|
||||||
CipherOutputStream cyos = null;
|
ChunkedCipherOutputStream cyos = null;
|
||||||
|
|
||||||
private static final BitField fieldRecInst = new BitField(0xFFF0);
|
private static final BitField fieldRecInst = new BitField(0xFFF0);
|
||||||
|
|
||||||
|
private static final int BLIB_STORE_ENTRY_PARTS[] = {
|
||||||
|
1, // btWin32
|
||||||
|
1, // btMacOS
|
||||||
|
16, // rgbUid
|
||||||
|
2, // tag
|
||||||
|
4, // size
|
||||||
|
4, // cRef
|
||||||
|
4, // foDelay
|
||||||
|
1, // unused1
|
||||||
|
1, // cbName (@ index 33)
|
||||||
|
1, // unused2
|
||||||
|
1, // unused3
|
||||||
|
};
|
||||||
|
|
||||||
protected HSLFSlideShowEncrypted(DocumentEncryptionAtom dea) {
|
protected HSLFSlideShowEncrypted(DocumentEncryptionAtom dea) {
|
||||||
this.dea = dea;
|
this.dea = dea;
|
||||||
@ -67,7 +85,9 @@ public class HSLFSlideShowEncrypted {
|
|||||||
UserEditAtom userEditAtomWithEncryption = null;
|
UserEditAtom userEditAtomWithEncryption = null;
|
||||||
for (Map.Entry<Integer, Record> me : recordMap.descendingMap().entrySet()) {
|
for (Map.Entry<Integer, Record> me : recordMap.descendingMap().entrySet()) {
|
||||||
Record r = me.getValue();
|
Record r = me.getValue();
|
||||||
if (!(r instanceof UserEditAtom)) continue;
|
if (!(r instanceof UserEditAtom)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
UserEditAtom uea = (UserEditAtom)r;
|
UserEditAtom uea = (UserEditAtom)r;
|
||||||
if (uea.getEncryptSessionPersistIdRef() != -1) {
|
if (uea.getEncryptSessionPersistIdRef() != -1) {
|
||||||
userEditAtomWithEncryption = uea;
|
userEditAtomWithEncryption = uea;
|
||||||
@ -83,7 +103,7 @@ public class HSLFSlideShowEncrypted {
|
|||||||
Record r = recordMap.get(userEditAtomWithEncryption.getPersistPointersOffset());
|
Record r = recordMap.get(userEditAtomWithEncryption.getPersistPointersOffset());
|
||||||
assert(r instanceof PersistPtrHolder);
|
assert(r instanceof PersistPtrHolder);
|
||||||
PersistPtrHolder ptr = (PersistPtrHolder)r;
|
PersistPtrHolder ptr = (PersistPtrHolder)r;
|
||||||
|
|
||||||
Integer encOffset = ptr.getSlideLocationsLookup().get(userEditAtomWithEncryption.getEncryptSessionPersistIdRef());
|
Integer encOffset = ptr.getSlideLocationsLookup().get(userEditAtomWithEncryption.getEncryptSessionPersistIdRef());
|
||||||
if (encOffset == null) {
|
if (encOffset == null) {
|
||||||
// encryption info doesn't exist anymore
|
// encryption info doesn't exist anymore
|
||||||
@ -91,7 +111,7 @@ public class HSLFSlideShowEncrypted {
|
|||||||
dea = null;
|
dea = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
r = recordMap.get(encOffset);
|
r = recordMap.get(encOffset);
|
||||||
if (r == null) {
|
if (r == null) {
|
||||||
r = Record.buildRecordAtOffset(docstream, encOffset);
|
r = Record.buildRecordAtOffset(docstream, encOffset);
|
||||||
@ -100,7 +120,7 @@ public class HSLFSlideShowEncrypted {
|
|||||||
assert(r instanceof DocumentEncryptionAtom);
|
assert(r instanceof DocumentEncryptionAtom);
|
||||||
this.dea = (DocumentEncryptionAtom)r;
|
this.dea = (DocumentEncryptionAtom)r;
|
||||||
decryptInit();
|
decryptInit();
|
||||||
|
|
||||||
String pass = Biff8EncryptionKey.getCurrentUserPassword();
|
String pass = Biff8EncryptionKey.getCurrentUserPassword();
|
||||||
if(!dec.verifyPassword(pass != null ? pass : Decryptor.DEFAULT_PASSWORD)) {
|
if(!dec.verifyPassword(pass != null ? pass : Decryptor.DEFAULT_PASSWORD)) {
|
||||||
throw new EncryptedPowerPointFileException("PowerPoint file is encrypted. The correct password needs to be set via Biff8EncryptionKey.setCurrentUserPassword()");
|
throw new EncryptedPowerPointFileException("PowerPoint file is encrypted. The correct password needs to be set via Biff8EncryptionKey.setCurrentUserPassword()");
|
||||||
@ -110,119 +130,144 @@ public class HSLFSlideShowEncrypted {
|
|||||||
public DocumentEncryptionAtom getDocumentEncryptionAtom() {
|
public DocumentEncryptionAtom getDocumentEncryptionAtom() {
|
||||||
return dea;
|
return dea;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setPersistId(int persistId) {
|
|
||||||
if (enc != null && dec != null) {
|
|
||||||
throw new EncryptedPowerPointFileException("Use instance either for en- or decryption");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (enc != null) cipher = enc.initCipherForBlock(cipher, persistId);
|
|
||||||
if (dec != null) cipher = dec.initCipherForBlock(cipher, persistId);
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
throw new EncryptedPowerPointFileException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void decryptInit() {
|
protected void decryptInit() {
|
||||||
if (dec != null) return;
|
if (dec != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
EncryptionInfo ei = dea.getEncryptionInfo();
|
EncryptionInfo ei = dea.getEncryptionInfo();
|
||||||
dec = (CryptoAPIDecryptor)ei.getDecryptor();
|
dec = (CryptoAPIDecryptor)ei.getDecryptor();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void encryptInit() {
|
protected void encryptInit() {
|
||||||
if (enc != null) return;
|
if (enc != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
EncryptionInfo ei = dea.getEncryptionInfo();
|
EncryptionInfo ei = dea.getEncryptionInfo();
|
||||||
enc = (CryptoAPIEncryptor)ei.getEncryptor();
|
enc = (CryptoAPIEncryptor)ei.getEncryptor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected OutputStream encryptRecord(OutputStream plainStream, int persistId, Record record) {
|
protected OutputStream encryptRecord(OutputStream plainStream, int persistId, Record record) {
|
||||||
boolean isPlain = (dea == null
|
boolean isPlain = (dea == null
|
||||||
|| record instanceof UserEditAtom
|
|| record instanceof UserEditAtom
|
||||||
|| record instanceof PersistPtrHolder
|
|| record instanceof PersistPtrHolder
|
||||||
|| record instanceof DocumentEncryptionAtom
|
|| record instanceof DocumentEncryptionAtom
|
||||||
);
|
);
|
||||||
if (isPlain) return plainStream;
|
|
||||||
|
|
||||||
encryptInit();
|
try {
|
||||||
setPersistId(persistId);
|
if (isPlain) {
|
||||||
|
if (cyos != null) {
|
||||||
if (cyos == null) {
|
// write cached data to stream
|
||||||
cyos = new CipherOutputStream(plainStream, cipher);
|
cyos.flush();
|
||||||
|
}
|
||||||
|
return plainStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptInit();
|
||||||
|
|
||||||
|
if (cyos == null) {
|
||||||
|
enc.setChunkSize(-1);
|
||||||
|
cyos = enc.getDataStream(plainStream, 0);
|
||||||
|
}
|
||||||
|
cyos.initCipherForBlock(persistId, false);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new EncryptedPowerPointFileException(e);
|
||||||
}
|
}
|
||||||
return cyos;
|
return cyos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void readFully(ChunkedCipherInputStream ccis, byte[] docstream, int offset, int len) throws IOException {
|
||||||
|
if (IOUtils.readFully(ccis, docstream, offset, len) == -1) {
|
||||||
|
throw new EncryptedPowerPointFileException("unexpected EOF");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void decryptRecord(byte[] docstream, int persistId, int offset) {
|
protected void decryptRecord(byte[] docstream, int persistId, int offset) {
|
||||||
if (dea == null) return;
|
if (dea == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
decryptInit();
|
decryptInit();
|
||||||
setPersistId(persistId);
|
dec.setChunkSize(-1);
|
||||||
|
LittleEndianByteArrayInputStream lei = new LittleEndianByteArrayInputStream(docstream, offset);
|
||||||
|
ChunkedCipherInputStream ccis = null;
|
||||||
try {
|
try {
|
||||||
|
ccis = dec.getDataStream(lei, docstream.length-offset, 0);
|
||||||
|
ccis.initCipherForBlock(persistId);
|
||||||
|
|
||||||
// decrypt header and read length to be decrypted
|
// decrypt header and read length to be decrypted
|
||||||
cipher.update(docstream, offset, 8, docstream, offset);
|
readFully(ccis, docstream, offset, 8);
|
||||||
// decrypt the rest of the record
|
// decrypt the rest of the record
|
||||||
int rlen = (int)LittleEndian.getUInt(docstream, offset+4);
|
int rlen = (int)LittleEndian.getUInt(docstream, offset+4);
|
||||||
cipher.update(docstream, offset+8, rlen, docstream, offset+8);
|
readFully(ccis, docstream, offset+8, rlen);
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
throw new CorruptPowerPointFileException(e);
|
} catch (Exception e) {
|
||||||
}
|
throw new EncryptedPowerPointFileException(e);
|
||||||
}
|
} finally {
|
||||||
|
try {
|
||||||
|
if (ccis != null) {
|
||||||
|
ccis.close();
|
||||||
|
}
|
||||||
|
lei.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new EncryptedPowerPointFileException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void decryptPicBytes(byte[] pictstream, int offset, int len)
|
||||||
|
throws IOException, GeneralSecurityException {
|
||||||
|
// when reading the picture elements, each time a segment is read, the cipher needs
|
||||||
|
// to be reset (usually done when calling Cipher.doFinal)
|
||||||
|
LittleEndianByteArrayInputStream lei = new LittleEndianByteArrayInputStream(pictstream, offset);
|
||||||
|
ChunkedCipherInputStream ccis = dec.getDataStream(lei, len, 0);
|
||||||
|
readFully(ccis, pictstream, offset, len);
|
||||||
|
ccis.close();
|
||||||
|
lei.close();
|
||||||
|
}
|
||||||
|
|
||||||
protected void decryptPicture(byte[] pictstream, int offset) {
|
protected void decryptPicture(byte[] pictstream, int offset) {
|
||||||
if (dea == null) return;
|
if (dea == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
decryptInit();
|
decryptInit();
|
||||||
setPersistId(0);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// decrypt header and read length to be decrypted
|
// decrypt header and read length to be decrypted
|
||||||
cipher.doFinal(pictstream, offset, 8, pictstream, offset);
|
decryptPicBytes(pictstream, offset, 8);
|
||||||
int recInst = fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset));
|
int recInst = fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset));
|
||||||
int recType = LittleEndian.getUShort(pictstream, offset+2);
|
int recType = LittleEndian.getUShort(pictstream, offset+2);
|
||||||
int rlen = (int)LittleEndian.getUInt(pictstream, offset+4);
|
int rlen = (int)LittleEndian.getUInt(pictstream, offset+4);
|
||||||
offset += 8;
|
offset += 8;
|
||||||
int endOffset = offset + rlen;
|
int endOffset = offset + rlen;
|
||||||
|
|
||||||
if (recType == 0xF007) {
|
if (recType == 0xF007) {
|
||||||
// TOOD: get a real example file ... to actual test the FBSE entry
|
// TOOD: get a real example file ... to actual test the FBSE entry
|
||||||
// not sure where the foDelay block is
|
// not sure where the foDelay block is
|
||||||
|
|
||||||
// File BLIP Store Entry (FBSE)
|
// File BLIP Store Entry (FBSE)
|
||||||
cipher.doFinal(pictstream, offset, 1, pictstream, offset); // btWin32
|
for (int part : BLIB_STORE_ENTRY_PARTS) {
|
||||||
offset++;
|
decryptPicBytes(pictstream, offset, part);
|
||||||
cipher.doFinal(pictstream, offset, 1, pictstream, offset); // btMacOS
|
}
|
||||||
offset++;
|
offset += 36;
|
||||||
cipher.doFinal(pictstream, offset, 16, pictstream, offset); // rgbUid
|
|
||||||
offset += 16;
|
int cbName = LittleEndian.getUShort(pictstream, offset-3);
|
||||||
cipher.doFinal(pictstream, offset, 2, pictstream, offset); // tag
|
|
||||||
offset += 2;
|
|
||||||
cipher.doFinal(pictstream, offset, 4, pictstream, offset); // size
|
|
||||||
offset += 4;
|
|
||||||
cipher.doFinal(pictstream, offset, 4, pictstream, offset); // cRef
|
|
||||||
offset += 4;
|
|
||||||
cipher.doFinal(pictstream, offset, 4, pictstream, offset); // foDelay
|
|
||||||
offset += 4;
|
|
||||||
cipher.doFinal(pictstream, offset+0, 1, pictstream, offset+0); // unused1
|
|
||||||
cipher.doFinal(pictstream, offset+1, 1, pictstream, offset+1); // cbName
|
|
||||||
cipher.doFinal(pictstream, offset+2, 1, pictstream, offset+2); // unused2
|
|
||||||
cipher.doFinal(pictstream, offset+3, 1, pictstream, offset+3); // unused3
|
|
||||||
int cbName = LittleEndian.getUShort(pictstream, offset+1);
|
|
||||||
offset += 4;
|
|
||||||
if (cbName > 0) {
|
if (cbName > 0) {
|
||||||
cipher.doFinal(pictstream, offset, cbName, pictstream, offset); // nameData
|
// read nameData
|
||||||
|
decryptPicBytes(pictstream, offset, cbName);
|
||||||
offset += cbName;
|
offset += cbName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (offset == endOffset) {
|
if (offset == endOffset) {
|
||||||
return; // no embedded blip
|
return; // no embedded blip
|
||||||
}
|
}
|
||||||
// fall through, read embedded blip now
|
// fall through, read embedded blip now
|
||||||
|
|
||||||
// update header data
|
// update header data
|
||||||
cipher.doFinal(pictstream, offset, 8, pictstream, offset);
|
decryptPicBytes(pictstream, offset, 8);
|
||||||
recInst = fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset));
|
recInst = fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset));
|
||||||
recType = LittleEndian.getUShort(pictstream, offset+2);
|
recType = LittleEndian.getUShort(pictstream, offset+2);
|
||||||
// rlen = (int)LittleEndian.getUInt(pictstream, offset+4);
|
// rlen = (int)LittleEndian.getUInt(pictstream, offset+4);
|
||||||
@ -231,70 +276,73 @@ public class HSLFSlideShowEncrypted {
|
|||||||
|
|
||||||
int rgbUidCnt = (recInst == 0x217 || recInst == 0x3D5 || recInst == 0x46B || recInst == 0x543 ||
|
int rgbUidCnt = (recInst == 0x217 || recInst == 0x3D5 || recInst == 0x46B || recInst == 0x543 ||
|
||||||
recInst == 0x6E1 || recInst == 0x6E3 || recInst == 0x6E5 || recInst == 0x7A9) ? 2 : 1;
|
recInst == 0x6E1 || recInst == 0x6E3 || recInst == 0x6E5 || recInst == 0x7A9) ? 2 : 1;
|
||||||
|
|
||||||
|
// rgbUid 1/2
|
||||||
for (int i=0; i<rgbUidCnt; i++) {
|
for (int i=0; i<rgbUidCnt; i++) {
|
||||||
cipher.doFinal(pictstream, offset, 16, pictstream, offset); // rgbUid 1/2
|
decryptPicBytes(pictstream, offset, 16);
|
||||||
offset += 16;
|
offset += 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int nextBytes;
|
||||||
if (recType == 0xF01A || recType == 0XF01B || recType == 0XF01C) {
|
if (recType == 0xF01A || recType == 0XF01B || recType == 0XF01C) {
|
||||||
cipher.doFinal(pictstream, offset, 34, pictstream, offset); // metafileHeader
|
// metafileHeader
|
||||||
offset += 34;
|
nextBytes = 34;
|
||||||
} else {
|
} else {
|
||||||
cipher.doFinal(pictstream, offset, 1, pictstream, offset); // tag
|
// tag
|
||||||
offset += 1;
|
nextBytes = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
decryptPicBytes(pictstream, offset, nextBytes);
|
||||||
|
offset += nextBytes;
|
||||||
|
|
||||||
int blipLen = endOffset - offset;
|
int blipLen = endOffset - offset;
|
||||||
cipher.doFinal(pictstream, offset, blipLen, pictstream, offset);
|
decryptPicBytes(pictstream, offset, blipLen);
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (Exception e) {
|
||||||
throw new CorruptPowerPointFileException(e);
|
throw new CorruptPowerPointFileException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void encryptPicture(byte[] pictstream, int offset) {
|
protected void encryptPicture(byte[] pictstream, int offset) {
|
||||||
if (dea == null) return;
|
if (dea == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
encryptInit();
|
encryptInit();
|
||||||
setPersistId(0);
|
|
||||||
|
LittleEndianByteArrayOutputStream los = new LittleEndianByteArrayOutputStream(pictstream, offset);
|
||||||
|
ChunkedCipherOutputStream ccos = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
enc.setChunkSize(-1);
|
||||||
|
ccos = enc.getDataStream(los, 0);
|
||||||
int recInst = fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset));
|
int recInst = fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset));
|
||||||
int recType = LittleEndian.getUShort(pictstream, offset+2);
|
int recType = LittleEndian.getUShort(pictstream, offset+2);
|
||||||
int rlen = (int)LittleEndian.getUInt(pictstream, offset+4);
|
final int rlen = (int)LittleEndian.getUInt(pictstream, offset+4);
|
||||||
cipher.doFinal(pictstream, offset, 8, pictstream, offset);
|
|
||||||
|
ccos.write(pictstream, offset, 8);
|
||||||
|
ccos.flush();
|
||||||
offset += 8;
|
offset += 8;
|
||||||
int endOffset = offset + rlen;
|
int endOffset = offset + rlen;
|
||||||
|
|
||||||
if (recType == 0xF007) {
|
if (recType == 0xF007) {
|
||||||
// TOOD: get a real example file ... to actual test the FBSE entry
|
// TOOD: get a real example file ... to actual test the FBSE entry
|
||||||
// not sure where the foDelay block is
|
// not sure where the foDelay block is
|
||||||
|
|
||||||
// File BLIP Store Entry (FBSE)
|
// File BLIP Store Entry (FBSE)
|
||||||
cipher.doFinal(pictstream, offset, 1, pictstream, offset); // btWin32
|
int cbName = LittleEndian.getUShort(pictstream, offset+33);
|
||||||
offset++;
|
|
||||||
cipher.doFinal(pictstream, offset, 1, pictstream, offset); // btMacOS
|
for (int part : BLIB_STORE_ENTRY_PARTS) {
|
||||||
offset++;
|
ccos.write(pictstream, offset, part);
|
||||||
cipher.doFinal(pictstream, offset, 16, pictstream, offset); // rgbUid
|
ccos.flush();
|
||||||
offset += 16;
|
offset += part;
|
||||||
cipher.doFinal(pictstream, offset, 2, pictstream, offset); // tag
|
}
|
||||||
offset += 2;
|
|
||||||
cipher.doFinal(pictstream, offset, 4, pictstream, offset); // size
|
|
||||||
offset += 4;
|
|
||||||
cipher.doFinal(pictstream, offset, 4, pictstream, offset); // cRef
|
|
||||||
offset += 4;
|
|
||||||
cipher.doFinal(pictstream, offset, 4, pictstream, offset); // foDelay
|
|
||||||
offset += 4;
|
|
||||||
int cbName = LittleEndian.getUShort(pictstream, offset+1);
|
|
||||||
cipher.doFinal(pictstream, offset+0, 1, pictstream, offset+0); // unused1
|
|
||||||
cipher.doFinal(pictstream, offset+1, 1, pictstream, offset+1); // cbName
|
|
||||||
cipher.doFinal(pictstream, offset+2, 1, pictstream, offset+2); // unused2
|
|
||||||
cipher.doFinal(pictstream, offset+3, 1, pictstream, offset+3); // unused3
|
|
||||||
offset += 4;
|
|
||||||
if (cbName > 0) {
|
if (cbName > 0) {
|
||||||
cipher.doFinal(pictstream, offset, cbName, pictstream, offset); // nameData
|
ccos.write(pictstream, offset, cbName);
|
||||||
|
ccos.flush();
|
||||||
offset += cbName;
|
offset += cbName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (offset == endOffset) {
|
if (offset == endOffset) {
|
||||||
return; // no embedded blip
|
return; // no embedded blip
|
||||||
}
|
}
|
||||||
@ -303,32 +351,45 @@ public class HSLFSlideShowEncrypted {
|
|||||||
// update header data
|
// update header data
|
||||||
recInst = fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset));
|
recInst = fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset));
|
||||||
recType = LittleEndian.getUShort(pictstream, offset+2);
|
recType = LittleEndian.getUShort(pictstream, offset+2);
|
||||||
// rlen = (int) LittleEndian.getUInt(pictstream, offset+4);
|
ccos.write(pictstream, offset, 8);
|
||||||
cipher.doFinal(pictstream, offset, 8, pictstream, offset);
|
ccos.flush();
|
||||||
offset += 8;
|
offset += 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
int rgbUidCnt = (recInst == 0x217 || recInst == 0x3D5 || recInst == 0x46B || recInst == 0x543 ||
|
int rgbUidCnt = (recInst == 0x217 || recInst == 0x3D5 || recInst == 0x46B || recInst == 0x543 ||
|
||||||
recInst == 0x6E1 || recInst == 0x6E3 || recInst == 0x6E5 || recInst == 0x7A9) ? 2 : 1;
|
recInst == 0x6E1 || recInst == 0x6E3 || recInst == 0x6E5 || recInst == 0x7A9) ? 2 : 1;
|
||||||
|
|
||||||
for (int i=0; i<rgbUidCnt; i++) {
|
for (int i=0; i<rgbUidCnt; i++) {
|
||||||
cipher.doFinal(pictstream, offset, 16, pictstream, offset); // rgbUid 1/2
|
ccos.write(pictstream, offset, 16); // rgbUid 1/2
|
||||||
|
ccos.flush();
|
||||||
offset += 16;
|
offset += 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recType == 0xF01A || recType == 0XF01B || recType == 0XF01C) {
|
if (recType == 0xF01A || recType == 0XF01B || recType == 0XF01C) {
|
||||||
cipher.doFinal(pictstream, offset, 34, pictstream, offset); // metafileHeader
|
ccos.write(pictstream, offset, 34); // metafileHeader
|
||||||
offset += 34;
|
offset += 34;
|
||||||
|
ccos.flush();
|
||||||
} else {
|
} else {
|
||||||
cipher.doFinal(pictstream, offset, 1, pictstream, offset); // tag
|
ccos.write(pictstream, offset, 1); // tag
|
||||||
offset += 1;
|
offset += 1;
|
||||||
|
ccos.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
int blipLen = endOffset - offset;
|
int blipLen = endOffset - offset;
|
||||||
cipher.doFinal(pictstream, offset, blipLen, pictstream, offset);
|
ccos.write(pictstream, offset, blipLen);
|
||||||
} catch (GeneralSecurityException e) {
|
ccos.flush();
|
||||||
throw new CorruptPowerPointFileException(e);
|
} catch (Exception e) {
|
||||||
}
|
throw new EncryptedPowerPointFileException(e);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (ccos != null) {
|
||||||
|
ccos.close();
|
||||||
|
}
|
||||||
|
los.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new EncryptedPowerPointFileException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Record[] updateEncryptionRecord(Record records[]) {
|
protected Record[] updateEncryptionRecord(Record records[]) {
|
||||||
@ -372,7 +433,7 @@ public class HSLFSlideShowEncrypted {
|
|||||||
protected static Record[] normalizeRecords(Record records[]) {
|
protected static Record[] normalizeRecords(Record records[]) {
|
||||||
// http://msdn.microsoft.com/en-us/library/office/gg615594(v=office.14).aspx
|
// http://msdn.microsoft.com/en-us/library/office/gg615594(v=office.14).aspx
|
||||||
// repeated slideIds can be overwritten, i.e. ignored
|
// repeated slideIds can be overwritten, i.e. ignored
|
||||||
|
|
||||||
UserEditAtom uea = null;
|
UserEditAtom uea = null;
|
||||||
PersistPtrHolder pph = null;
|
PersistPtrHolder pph = null;
|
||||||
TreeMap<Integer,Integer> slideLocations = new TreeMap<Integer,Integer>();
|
TreeMap<Integer,Integer> slideLocations = new TreeMap<Integer,Integer>();
|
||||||
@ -386,7 +447,7 @@ public class HSLFSlideShowEncrypted {
|
|||||||
uea = (UserEditAtom)pdr;
|
uea = (UserEditAtom)pdr;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pdr instanceof PersistPtrHolder) {
|
if (pdr instanceof PersistPtrHolder) {
|
||||||
if (pph != null) {
|
if (pph != null) {
|
||||||
duplicatedCount++;
|
duplicatedCount++;
|
||||||
@ -394,16 +455,18 @@ public class HSLFSlideShowEncrypted {
|
|||||||
pph = (PersistPtrHolder)pdr;
|
pph = (PersistPtrHolder)pdr;
|
||||||
for (Map.Entry<Integer,Integer> me : pph.getSlideLocationsLookup().entrySet()) {
|
for (Map.Entry<Integer,Integer> me : pph.getSlideLocationsLookup().entrySet()) {
|
||||||
Integer oldOffset = slideLocations.put(me.getKey(), me.getValue());
|
Integer oldOffset = slideLocations.put(me.getKey(), me.getValue());
|
||||||
if (oldOffset != null) obsoleteOffsets.add(oldOffset);
|
if (oldOffset != null) {
|
||||||
|
obsoleteOffsets.add(oldOffset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
recordMap.put(pdr.getLastOnDiskOffset(), r);
|
recordMap.put(pdr.getLastOnDiskOffset(), r);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(uea != null && pph != null && uea.getPersistPointersOffset() == pph.getLastOnDiskOffset());
|
assert(uea != null && pph != null && uea.getPersistPointersOffset() == pph.getLastOnDiskOffset());
|
||||||
|
|
||||||
recordMap.put(pph.getLastOnDiskOffset(), pph);
|
recordMap.put(pph.getLastOnDiskOffset(), pph);
|
||||||
recordMap.put(uea.getLastOnDiskOffset(), uea);
|
recordMap.put(uea.getLastOnDiskOffset(), uea);
|
||||||
|
|
||||||
@ -416,15 +479,15 @@ public class HSLFSlideShowEncrypted {
|
|||||||
for (Map.Entry<Integer,Integer> me : slideLocations.entrySet()) {
|
for (Map.Entry<Integer,Integer> me : slideLocations.entrySet()) {
|
||||||
pph.addSlideLookup(me.getKey(), me.getValue());
|
pph.addSlideLookup(me.getKey(), me.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Integer oldOffset : obsoleteOffsets) {
|
for (Integer oldOffset : obsoleteOffsets) {
|
||||||
recordMap.remove(oldOffset);
|
recordMap.remove(oldOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
return recordMap.values().toArray(new Record[recordMap.size()]);
|
return recordMap.values().toArray(new Record[recordMap.size()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected static Record[] removeEncryptionRecord(Record records[]) {
|
protected static Record[] removeEncryptionRecord(Record records[]) {
|
||||||
int deaSlideId = -1;
|
int deaSlideId = -1;
|
||||||
int deaOffset = -1;
|
int deaOffset = -1;
|
||||||
@ -444,23 +507,27 @@ public class HSLFSlideShowEncrypted {
|
|||||||
}
|
}
|
||||||
recordList.add(r);
|
recordList.add(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(ptr != null);
|
assert(ptr != null);
|
||||||
if (deaSlideId == -1 && deaOffset == -1) return records;
|
if (deaSlideId == -1 && deaOffset == -1) {
|
||||||
|
return records;
|
||||||
|
}
|
||||||
|
|
||||||
TreeMap<Integer,Integer> tm = new TreeMap<Integer,Integer>(ptr.getSlideLocationsLookup());
|
TreeMap<Integer,Integer> tm = new TreeMap<Integer,Integer>(ptr.getSlideLocationsLookup());
|
||||||
ptr.clear();
|
ptr.clear();
|
||||||
int maxSlideId = -1;
|
int maxSlideId = -1;
|
||||||
for (Map.Entry<Integer,Integer> me : tm.entrySet()) {
|
for (Map.Entry<Integer,Integer> me : tm.entrySet()) {
|
||||||
if (me.getKey() == deaSlideId || me.getValue() == deaOffset) continue;
|
if (me.getKey() == deaSlideId || me.getValue() == deaOffset) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
ptr.addSlideLookup(me.getKey(), me.getValue());
|
ptr.addSlideLookup(me.getKey(), me.getValue());
|
||||||
maxSlideId = Math.max(me.getKey(), maxSlideId);
|
maxSlideId = Math.max(me.getKey(), maxSlideId);
|
||||||
}
|
}
|
||||||
|
|
||||||
uea.setMaxPersistWritten(maxSlideId);
|
uea.setMaxPersistWritten(maxSlideId);
|
||||||
|
|
||||||
records = recordList.toArray(new Record[recordList.size()]);
|
records = recordList.toArray(new Record[recordList.size()]);
|
||||||
|
|
||||||
return records;
|
return records;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -470,9 +537,13 @@ public class HSLFSlideShowEncrypted {
|
|||||||
int ueaIdx = -1, ptrIdx = -1, deaIdx = -1, idx = -1;
|
int ueaIdx = -1, ptrIdx = -1, deaIdx = -1, idx = -1;
|
||||||
for (Record r : records) {
|
for (Record r : records) {
|
||||||
idx++;
|
idx++;
|
||||||
if (r instanceof UserEditAtom) ueaIdx = idx;
|
if (r instanceof UserEditAtom) {
|
||||||
else if (r instanceof PersistPtrHolder) ptrIdx = idx;
|
ueaIdx = idx;
|
||||||
else if (r instanceof DocumentEncryptionAtom) deaIdx = idx;
|
} else if (r instanceof PersistPtrHolder) {
|
||||||
|
ptrIdx = idx;
|
||||||
|
} else if (r instanceof DocumentEncryptionAtom) {
|
||||||
|
deaIdx = idx;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
assert(ueaIdx != -1 && ptrIdx != -1 && ptrIdx < ueaIdx);
|
assert(ueaIdx != -1 && ptrIdx != -1 && ptrIdx < ueaIdx);
|
||||||
if (deaIdx != -1) {
|
if (deaIdx != -1) {
|
||||||
@ -488,13 +559,22 @@ public class HSLFSlideShowEncrypted {
|
|||||||
ptr.addSlideLookup(nextSlideId, ptr.getLastOnDiskOffset()-1);
|
ptr.addSlideLookup(nextSlideId, ptr.getLastOnDiskOffset()-1);
|
||||||
uea.setEncryptSessionPersistIdRef(nextSlideId);
|
uea.setEncryptSessionPersistIdRef(nextSlideId);
|
||||||
uea.setMaxPersistWritten(nextSlideId);
|
uea.setMaxPersistWritten(nextSlideId);
|
||||||
|
|
||||||
Record newRecords[] = new Record[records.length+1];
|
Record newRecords[] = new Record[records.length+1];
|
||||||
if (ptrIdx > 0) System.arraycopy(records, 0, newRecords, 0, ptrIdx);
|
if (ptrIdx > 0) {
|
||||||
if (ptrIdx < records.length-1) System.arraycopy(records, ptrIdx, newRecords, ptrIdx+1, records.length-ptrIdx);
|
System.arraycopy(records, 0, newRecords, 0, ptrIdx);
|
||||||
|
}
|
||||||
|
if (ptrIdx < records.length-1) {
|
||||||
|
System.arraycopy(records, ptrIdx, newRecords, ptrIdx+1, records.length-ptrIdx);
|
||||||
|
}
|
||||||
newRecords[ptrIdx] = dea;
|
newRecords[ptrIdx] = dea;
|
||||||
return newRecords;
|
return newRecords;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void close() throws IOException {
|
||||||
|
if (cyos != null) {
|
||||||
|
cyos.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,7 @@ import org.apache.poi.poifs.filesystem.EntryUtils;
|
|||||||
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
|
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
|
||||||
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
||||||
import org.apache.poi.sl.usermodel.PictureData.PictureType;
|
import org.apache.poi.sl.usermodel.PictureData.PictureType;
|
||||||
|
import org.apache.poi.util.IOUtils;
|
||||||
import org.apache.poi.util.LittleEndian;
|
import org.apache.poi.util.LittleEndian;
|
||||||
import org.apache.poi.util.POILogFactory;
|
import org.apache.poi.util.POILogFactory;
|
||||||
import org.apache.poi.util.POILogger;
|
import org.apache.poi.util.POILogger;
|
||||||
@ -205,14 +206,12 @@ public final class HSLFSlideShowImpl extends POIDocument implements Closeable {
|
|||||||
|
|
||||||
// Grab the document stream
|
// Grab the document stream
|
||||||
int len = docProps.getSize();
|
int len = docProps.getSize();
|
||||||
_docstream = new byte[len];
|
InputStream is = directory.createDocumentInputStream("PowerPoint Document");
|
||||||
InputStream is = directory.createDocumentInputStream("PowerPoint Document");
|
try {
|
||||||
int readLen = is.read(_docstream);
|
_docstream = IOUtils.toByteArray(is, len);
|
||||||
is.close();
|
} finally {
|
||||||
|
is.close();
|
||||||
if (len != readLen) {
|
}
|
||||||
throw new IOException("Document input stream ended prematurely - expected "+len+" bytes - received "+readLen+" bytes");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -364,17 +363,10 @@ public final class HSLFSlideShowImpl extends POIDocument implements Closeable {
|
|||||||
HSLFSlideShowEncrypted decryptData = new HSLFSlideShowEncrypted(getDocumentEncryptionAtom());
|
HSLFSlideShowEncrypted decryptData = new HSLFSlideShowEncrypted(getDocumentEncryptionAtom());
|
||||||
|
|
||||||
DocumentEntry entry = (DocumentEntry)directory.getEntry("Pictures");
|
DocumentEntry entry = (DocumentEntry)directory.getEntry("Pictures");
|
||||||
int len = entry.getSize();
|
DocumentInputStream is = directory.createDocumentInputStream(entry);
|
||||||
byte[] pictstream = new byte[len];
|
byte[] pictstream = IOUtils.toByteArray(is, entry.getSize());
|
||||||
DocumentInputStream is = directory.createDocumentInputStream(entry);
|
|
||||||
int readLen = is.read(pictstream);
|
|
||||||
is.close();
|
is.close();
|
||||||
|
|
||||||
if (len != readLen) {
|
|
||||||
throw new IOException("Picture stream ended prematurely - expected "+len+" bytes - received "+readLen+" bytes");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int pos = 0;
|
int pos = 0;
|
||||||
// An empty picture record (length 0) will take up 8 bytes
|
// An empty picture record (length 0) will take up 8 bytes
|
||||||
while (pos <= (pictstream.length-8)) {
|
while (pos <= (pictstream.length-8)) {
|
||||||
@ -512,7 +504,7 @@ public final class HSLFSlideShowImpl extends POIDocument implements Closeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HSLFSlideShowEncrypted encData = new HSLFSlideShowEncrypted(getDocumentEncryptionAtom());
|
HSLFSlideShowEncrypted encData = new HSLFSlideShowEncrypted(getDocumentEncryptionAtom());
|
||||||
|
|
||||||
for (Record record : _records) {
|
for (Record record : _records) {
|
||||||
assert(record instanceof PositionDependentRecord);
|
assert(record instanceof PositionDependentRecord);
|
||||||
// We've already figured out their new location, and
|
// We've already figured out their new location, and
|
||||||
@ -533,6 +525,8 @@ public final class HSLFSlideShowImpl extends POIDocument implements Closeable {
|
|||||||
record.writeOut(encData.encryptRecord(os, persistId, record));
|
record.writeOut(encData.encryptRecord(os, persistId, record));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
encData.close();
|
||||||
|
|
||||||
// Update and write out the Current User atom
|
// Update and write out the Current User atom
|
||||||
int oldLastUserEditAtomPos = (int)currentUser.getCurrentEditOffset();
|
int oldLastUserEditAtomPos = (int)currentUser.getCurrentEditOffset();
|
||||||
@ -733,7 +727,7 @@ public final class HSLFSlideShowImpl extends POIDocument implements Closeable {
|
|||||||
if (dea != null) {
|
if (dea != null) {
|
||||||
CryptoAPIEncryptor enc = (CryptoAPIEncryptor)dea.getEncryptionInfo().getEncryptor();
|
CryptoAPIEncryptor enc = (CryptoAPIEncryptor)dea.getEncryptionInfo().getEncryptor();
|
||||||
try {
|
try {
|
||||||
enc.getDataStream(outFS.getRoot()); // ignore OutputStream
|
enc.getSummaryEntries(outFS.getRoot()); // ignore OutputStream
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
|
@ -44,6 +44,7 @@ import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
|
|||||||
import org.apache.poi.poifs.crypt.CryptoFunctions;
|
import org.apache.poi.poifs.crypt.CryptoFunctions;
|
||||||
import org.apache.poi.poifs.crypt.EncryptionInfo;
|
import org.apache.poi.poifs.crypt.EncryptionInfo;
|
||||||
import org.apache.poi.poifs.crypt.HashAlgorithm;
|
import org.apache.poi.poifs.crypt.HashAlgorithm;
|
||||||
|
import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIDecryptor;
|
||||||
import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptionHeader;
|
import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptionHeader;
|
||||||
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
|
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
|
||||||
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
||||||
@ -176,7 +177,7 @@ public class TestDocumentEncryption {
|
|||||||
|
|
||||||
DocumentEncryptionAtom dea = hss.getDocumentEncryptionAtom();
|
DocumentEncryptionAtom dea = hss.getDocumentEncryptionAtom();
|
||||||
|
|
||||||
POIFSFileSystem fs2 = new POIFSFileSystem(dea.getEncryptionInfo().getDecryptor().getDataStream(fs));
|
POIFSFileSystem fs2 = ((CryptoAPIDecryptor)dea.getEncryptionInfo().getDecryptor()).getSummaryEntries(fs.getRoot(), "EncryptedSummary");
|
||||||
PropertySet ps = PropertySetFactory.create(fs2.getRoot(), SummaryInformation.DEFAULT_STREAM_NAME);
|
PropertySet ps = PropertySetFactory.create(fs2.getRoot(), SummaryInformation.DEFAULT_STREAM_NAME);
|
||||||
assertTrue(ps.isSummaryInformation());
|
assertTrue(ps.isSummaryInformation());
|
||||||
assertEquals("RC4 CryptoAPI Encryption", ps.getProperties()[1].getValue());
|
assertEquals("RC4 CryptoAPI Encryption", ps.getProperties()[1].getValue());
|
||||||
|
@ -21,8 +21,8 @@ import org.apache.poi.hssf.record.aggregates.AllRecordAggregateTests;
|
|||||||
import org.apache.poi.hssf.record.cf.TestCellRange;
|
import org.apache.poi.hssf.record.cf.TestCellRange;
|
||||||
import org.apache.poi.hssf.record.chart.AllChartRecordTests;
|
import org.apache.poi.hssf.record.chart.AllChartRecordTests;
|
||||||
import org.apache.poi.hssf.record.common.TestUnicodeString;
|
import org.apache.poi.hssf.record.common.TestUnicodeString;
|
||||||
import org.apache.poi.hssf.record.crypto.AllHSSFEncryptionTests;
|
|
||||||
import org.apache.poi.hssf.record.pivot.AllPivotRecordTests;
|
import org.apache.poi.hssf.record.pivot.AllPivotRecordTests;
|
||||||
|
import org.apache.poi.poifs.crypt.AllEncryptionTests;
|
||||||
import org.apache.poi.ss.formula.constant.TestConstantValueParser;
|
import org.apache.poi.ss.formula.constant.TestConstantValueParser;
|
||||||
import org.apache.poi.ss.formula.ptg.AllFormulaTests;
|
import org.apache.poi.ss.formula.ptg.AllFormulaTests;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@ -34,7 +34,7 @@ import org.junit.runners.Suite;
|
|||||||
@RunWith(Suite.class)
|
@RunWith(Suite.class)
|
||||||
@Suite.SuiteClasses({
|
@Suite.SuiteClasses({
|
||||||
AllChartRecordTests.class,
|
AllChartRecordTests.class,
|
||||||
AllHSSFEncryptionTests.class,
|
AllEncryptionTests.class,
|
||||||
AllFormulaTests.class,
|
AllFormulaTests.class,
|
||||||
AllPivotRecordTests.class,
|
AllPivotRecordTests.class,
|
||||||
AllRecordAggregateTests.class,
|
AllRecordAggregateTests.class,
|
||||||
|
@ -151,11 +151,12 @@ public final class TestRecordFactoryInputStream {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* makes sure the record stream starts with {@link BOFRecord} and then {@link WindowOneRecord}
|
* makes sure the record stream starts with {@link BOFRecord}, {@link FilePassRecord} and then {@link WindowOneRecord}
|
||||||
* The second record is gets decrypted so this method also checks its content.
|
* The third record is decrypted so this method also checks its content.
|
||||||
*/
|
*/
|
||||||
private void confirmReadInitialRecords(RecordFactoryInputStream rfis) {
|
private void confirmReadInitialRecords(RecordFactoryInputStream rfis) {
|
||||||
assertEquals(BOFRecord.class, rfis.nextRecord().getClass());
|
assertEquals(BOFRecord.class, rfis.nextRecord().getClass());
|
||||||
|
FilePassRecord recFP = (FilePassRecord) rfis.nextRecord();
|
||||||
WindowOneRecord rec1 = (WindowOneRecord) rfis.nextRecord();
|
WindowOneRecord rec1 = (WindowOneRecord) rfis.nextRecord();
|
||||||
assertArrayEquals(HexRead.readFromString(SAMPLE_WINDOW1),rec1.serialize());
|
assertArrayEquals(HexRead.readFromString(SAMPLE_WINDOW1),rec1.serialize());
|
||||||
}
|
}
|
||||||
|
@ -1,102 +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.util.Arrays;
|
|
||||||
|
|
||||||
import junit.framework.ComparisonFailure;
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
|
|
||||||
import org.apache.poi.util.HexDump;
|
|
||||||
import org.apache.poi.util.HexRead;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for {@link Biff8EncryptionKey}
|
|
||||||
*
|
|
||||||
* @author Josh Micich
|
|
||||||
*/
|
|
||||||
public final class TestBiff8EncryptionKey extends TestCase {
|
|
||||||
|
|
||||||
private static byte[] fromHex(String hexString) {
|
|
||||||
return HexRead.readFromString(hexString);
|
|
||||||
}
|
|
||||||
public void testCreateKeyDigest() {
|
|
||||||
byte[] docIdData = fromHex("17 F6 D1 6B 09 B1 5F 7B 4C 9D 03 B4 81 B5 B4 4A");
|
|
||||||
byte[] keyDigest = Biff8RC4Key.createKeyDigest("MoneyForNothing", docIdData);
|
|
||||||
byte[] expResult = fromHex("C2 D9 56 B2 6B");
|
|
||||||
if (!Arrays.equals(expResult, keyDigest)) {
|
|
||||||
throw new ComparisonFailure("keyDigest mismatch", HexDump.toHex(expResult), HexDump.toHex(keyDigest));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void testValidateWithDefaultPassword() {
|
|
||||||
|
|
||||||
String docIdSuffixA = "F 35 52 38 0D 75 4A E6 85 C2 FD 78 CE 3D D1 B6"; // valid prefix is 'D'
|
|
||||||
String saltHashA = "30 38 BE 5E 93 C5 7E B4 5F 52 CD A1 C6 8F B6 2A";
|
|
||||||
String saltDataA = "D4 04 43 EC B7 A7 6F 6A D2 68 C7 DF CF A8 80 68";
|
|
||||||
|
|
||||||
String docIdB = "39 D7 80 41 DA E4 74 2C 8C 84 F9 4D 39 9A 19 2D";
|
|
||||||
String saltDataSuffixB = "3 EA 8D 52 11 11 37 D2 BD 55 4C 01 0A 47 6E EB"; // valid prefix is 'C'
|
|
||||||
String saltHashB = "96 19 F5 D0 F1 63 08 F1 3E 09 40 1E 87 F0 4E 16";
|
|
||||||
|
|
||||||
confirmValid(true, "D" + docIdSuffixA, saltDataA, saltHashA);
|
|
||||||
confirmValid(true, docIdB, "C" + saltDataSuffixB, saltHashB);
|
|
||||||
confirmValid(false, "E" + docIdSuffixA, saltDataA, saltHashA);
|
|
||||||
confirmValid(false, docIdB, "B" + saltDataSuffixB, saltHashB);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testValidateWithSuppliedPassword() {
|
|
||||||
|
|
||||||
String docId = "DF 35 52 38 0D 75 4A E6 85 C2 FD 78 CE 3D D1 B6";
|
|
||||||
String saltData = "D4 04 43 EC B7 A7 6F 6A D2 68 C7 DF CF A8 80 68";
|
|
||||||
String saltHashA = "8D C2 63 CC E1 1D E0 05 20 16 96 AF 48 59 94 64"; // for password '5ecret'
|
|
||||||
String saltHashB = "31 0B 0D A4 69 55 8E 27 A1 03 AD C9 AE F8 09 04"; // for password '5ecret'
|
|
||||||
|
|
||||||
confirmValid(true, docId, saltData, saltHashA, "5ecret");
|
|
||||||
confirmValid(false, docId, saltData, saltHashA, "Secret");
|
|
||||||
confirmValid(true, docId, saltData, saltHashB, "Secret");
|
|
||||||
confirmValid(false, docId, saltData, saltHashB, "secret");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static void confirmValid(boolean expectedResult,
|
|
||||||
String docIdHex, String saltDataHex, String saltHashHex) {
|
|
||||||
confirmValid(expectedResult, docIdHex, saltDataHex, saltHashHex, null);
|
|
||||||
}
|
|
||||||
private static void confirmValid(boolean expectedResult,
|
|
||||||
String docIdHex, String saltDataHex, String saltHashHex, String password) {
|
|
||||||
byte[] docId = fromHex(docIdHex);
|
|
||||||
byte[] saltData = fromHex(saltDataHex);
|
|
||||||
byte[] saltHash = fromHex(saltHashHex);
|
|
||||||
|
|
||||||
|
|
||||||
Biff8EncryptionKey key;
|
|
||||||
if (password == null) {
|
|
||||||
key = Biff8EncryptionKey.create(docId);
|
|
||||||
} else {
|
|
||||||
key = Biff8EncryptionKey.create(password, docId);
|
|
||||||
}
|
|
||||||
boolean actResult = key.validate(saltData, saltHash);
|
|
||||||
if (expectedResult) {
|
|
||||||
assertTrue("validate failed", actResult);
|
|
||||||
} else {
|
|
||||||
assertFalse("validate succeeded unexpectedly", actResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,70 @@
|
|||||||
|
/* ====================================================================
|
||||||
|
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.usermodel;
|
||||||
|
|
||||||
|
import static org.apache.poi.POITestCase.assertContains;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.poi.hssf.HSSFITestDataProvider;
|
||||||
|
import org.apache.poi.hssf.extractor.ExcelExtractor;
|
||||||
|
import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class TestCryptoAPI {
|
||||||
|
final HSSFITestDataProvider ssTests = HSSFITestDataProvider.instance;
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void resetPW() {
|
||||||
|
Biff8EncryptionKey.setCurrentUserPassword(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void bug59857() throws IOException {
|
||||||
|
// XOR-Obfuscation
|
||||||
|
// TODO: XOR-Obfuscation is currently flawed - although the de-/obfuscation initially works,
|
||||||
|
// it suddenly differs from the result of encrypted files via Office ...
|
||||||
|
// and only very small files can be opened without file validation errors
|
||||||
|
validateContent("xor-encryption-abc.xls", "abc", "Sheet1\n1\n2\n3\n");
|
||||||
|
|
||||||
|
// BinaryRC4
|
||||||
|
validateContent("password.xls", "password", "A ZIP bomb is a variant of mail-bombing. After most commercial mail servers began checking mail with anti-virus software and filtering certain malicious file types, trojan horse viruses tried to send themselves compressed into archives, such as ZIP, RAR or 7-Zip. Mail server software was then configured to unpack archives and check their contents as well. That gave black hats the idea to compose a \"bomb\" consisting of an enormous text file, containing, for example, only the letter z repeated millions of times. Such a file compresses into a relatively small archive, but its unpacking (especially by early versions of mail servers) would use a high amount of processing power, RAM and swap space, which could result in denial of service. Modern mail server computers usually have sufficient intelligence to recognize such attacks as well as sufficient processing power and memory space to process malicious attachments without interruption of service, though some are still susceptible to this technique if the ZIP bomb is mass-mailed.");
|
||||||
|
|
||||||
|
// CryptoAPI
|
||||||
|
validateContent("35897-type4.xls", "freedom", "Sheet1\nhello there!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateContent(String wbFile, String password, String textExpected) throws IOException {
|
||||||
|
Biff8EncryptionKey.setCurrentUserPassword(password);
|
||||||
|
HSSFWorkbook wb = ssTests.openSampleWorkbook(wbFile);
|
||||||
|
ExcelExtractor ee1 = new ExcelExtractor(wb);
|
||||||
|
String textActual = ee1.getText();
|
||||||
|
assertContains(textActual, textExpected);
|
||||||
|
|
||||||
|
Biff8EncryptionKey.setCurrentUserPassword("bla");
|
||||||
|
HSSFWorkbook wbBla = ssTests.writeOutAndReadBack(wb);
|
||||||
|
ExcelExtractor ee2 = new ExcelExtractor(wbBla);
|
||||||
|
textActual = ee2.getText();
|
||||||
|
assertContains(textActual, textExpected);
|
||||||
|
ee2.close();
|
||||||
|
ee1.close();
|
||||||
|
wbBla.close();
|
||||||
|
wb.close();
|
||||||
|
}
|
||||||
|
}
|
@ -15,20 +15,19 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
==================================================================== */
|
==================================================================== */
|
||||||
|
|
||||||
package org.apache.poi.hssf.record.crypto;
|
package org.apache.poi.poifs.crypt;
|
||||||
|
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.Suite;
|
import org.junit.runners.Suite;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collects all tests for package <tt>org.apache.poi.hssf.record.crypto</tt>.
|
* Collects all tests for package <tt>org.apache.poi.poifs.crypt</tt>.
|
||||||
*
|
|
||||||
* @author Josh Micich
|
|
||||||
*/
|
*/
|
||||||
@RunWith(Suite.class)
|
@RunWith(Suite.class)
|
||||||
@Suite.SuiteClasses({
|
@Suite.SuiteClasses({
|
||||||
TestBiff8DecryptingStream.class,
|
TestBiff8DecryptingStream.class,
|
||||||
TestBiff8EncryptionKey.class
|
TestCipherAlgorithm.class,
|
||||||
|
TestXorEncryption.class
|
||||||
})
|
})
|
||||||
public final class AllHSSFEncryptionTests {
|
public final class AllEncryptionTests {
|
||||||
}
|
}
|
@ -15,7 +15,7 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
==================================================================== */
|
==================================================================== */
|
||||||
|
|
||||||
package org.apache.poi.hssf.record.crypto;
|
package org.apache.poi.poifs.crypt;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
@ -23,17 +23,18 @@ import static org.junit.Assert.assertFalse;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import junit.framework.AssertionFailedError;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import junit.framework.ComparisonFailure;
|
|
||||||
|
|
||||||
|
import org.apache.poi.hssf.record.crypto.Biff8DecryptingStream;
|
||||||
import org.apache.poi.util.HexDump;
|
import org.apache.poi.util.HexDump;
|
||||||
import org.apache.poi.util.HexRead;
|
import org.apache.poi.util.HexRead;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import junit.framework.AssertionFailedError;
|
||||||
|
import junit.framework.ComparisonFailure;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link Biff8DecryptingStream}
|
* Tests for {@link Biff8DecryptingStream}
|
||||||
*
|
|
||||||
* @author Josh Micich
|
|
||||||
*/
|
*/
|
||||||
public final class TestBiff8DecryptingStream {
|
public final class TestBiff8DecryptingStream {
|
||||||
|
|
||||||
@ -49,12 +50,10 @@ public final class TestBiff8DecryptingStream {
|
|||||||
public MockStream(int initialValue) {
|
public MockStream(int initialValue) {
|
||||||
_initialValue = initialValue;
|
_initialValue = initialValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int read() {
|
public int read() {
|
||||||
return (_initialValue+_position++) & 0xFF;
|
return (_initialValue+_position++) & 0xFF;
|
||||||
}
|
}
|
||||||
public int getPosition() {
|
|
||||||
return _position;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class StreamTester {
|
private static final class StreamTester {
|
||||||
@ -70,7 +69,11 @@ public final class TestBiff8DecryptingStream {
|
|||||||
public StreamTester(MockStream ms, String keyDigestHex, int expectedFirstInt) {
|
public StreamTester(MockStream ms, String keyDigestHex, int expectedFirstInt) {
|
||||||
_ms = ms;
|
_ms = ms;
|
||||||
byte[] keyDigest = HexRead.readFromString(keyDigestHex);
|
byte[] keyDigest = HexRead.readFromString(keyDigestHex);
|
||||||
_bds = new Biff8DecryptingStream(_ms, 0, new Biff8RC4Key(keyDigest));
|
EncryptionInfo ei = new EncryptionInfo(EncryptionMode.binaryRC4);
|
||||||
|
Decryptor dec = ei.getDecryptor();
|
||||||
|
dec.setSecretKey(new SecretKeySpec(keyDigest, "RC4"));
|
||||||
|
|
||||||
|
_bds = new Biff8DecryptingStream(_ms, 0, ei);
|
||||||
assertEquals(expectedFirstInt, _bds.readInt());
|
assertEquals(expectedFirstInt, _bds.readInt());
|
||||||
_errorsOccurred = false;
|
_errorsOccurred = false;
|
||||||
}
|
}
|
||||||
@ -84,11 +87,11 @@ public final class TestBiff8DecryptingStream {
|
|||||||
* Also confirms that read position of the underlying stream is aligned.
|
* Also confirms that read position of the underlying stream is aligned.
|
||||||
*/
|
*/
|
||||||
public void rollForward(int fromPosition, int toPosition) {
|
public void rollForward(int fromPosition, int toPosition) {
|
||||||
assertEquals(fromPosition, _ms.getPosition());
|
assertEquals(fromPosition, _bds.getPosition());
|
||||||
for (int i = fromPosition; i < toPosition; i++) {
|
for (int i = fromPosition; i < toPosition; i++) {
|
||||||
_bds.readByte();
|
_bds.readByte();
|
||||||
}
|
}
|
||||||
assertEquals(toPosition, _ms.getPosition());
|
assertEquals(toPosition, _bds.getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void confirmByte(int expVal) {
|
public void confirmByte(int expVal) {
|
@ -17,14 +17,14 @@
|
|||||||
|
|
||||||
package org.apache.poi.poifs.crypt;
|
package org.apache.poi.poifs.crypt;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
import org.apache.poi.EncryptedDocumentException;
|
import org.apache.poi.EncryptedDocumentException;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class TestCipherAlgorithm {
|
public class TestCipherAlgorithm {
|
||||||
@Test
|
@Test
|
||||||
public void test() {
|
public void validInputs() {
|
||||||
assertEquals(128, CipherAlgorithm.aes128.defaultKeySize);
|
assertEquals(128, CipherAlgorithm.aes128.defaultKeySize);
|
||||||
|
|
||||||
for(CipherAlgorithm alg : CipherAlgorithm.values()) {
|
for(CipherAlgorithm alg : CipherAlgorithm.values()) {
|
||||||
@ -33,27 +33,20 @@ public class TestCipherAlgorithm {
|
|||||||
|
|
||||||
assertEquals(CipherAlgorithm.aes128, CipherAlgorithm.fromEcmaId(0x660E));
|
assertEquals(CipherAlgorithm.aes128, CipherAlgorithm.fromEcmaId(0x660E));
|
||||||
assertEquals(CipherAlgorithm.aes192, CipherAlgorithm.fromXmlId("AES", 192));
|
assertEquals(CipherAlgorithm.aes192, CipherAlgorithm.fromXmlId("AES", 192));
|
||||||
|
|
||||||
try {
|
|
||||||
CipherAlgorithm.fromEcmaId(0);
|
|
||||||
fail("Should throw exception");
|
|
||||||
} catch (EncryptedDocumentException e) {
|
|
||||||
// expected
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
CipherAlgorithm.fromXmlId("AES", 1);
|
|
||||||
fail("Should throw exception");
|
|
||||||
} catch (EncryptedDocumentException e) {
|
|
||||||
// expected
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
CipherAlgorithm.fromXmlId("RC1", 0x40);
|
|
||||||
fail("Should throw exception");
|
|
||||||
} catch (EncryptedDocumentException e) {
|
|
||||||
// expected
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected=EncryptedDocumentException.class)
|
||||||
|
public void invalidEcmaId() {
|
||||||
|
CipherAlgorithm.fromEcmaId(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected=EncryptedDocumentException.class)
|
||||||
|
public void invalidXmlId1() {
|
||||||
|
CipherAlgorithm.fromXmlId("AES", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected=EncryptedDocumentException.class)
|
||||||
|
public void invalidXmlId2() {
|
||||||
|
CipherAlgorithm.fromXmlId("RC1", 0x40);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,13 +15,14 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
==================================================================== */
|
==================================================================== */
|
||||||
|
|
||||||
package org.apache.poi.hssf.record.crypto;
|
package org.apache.poi.poifs.crypt;
|
||||||
|
|
||||||
import static org.hamcrest.core.IsEqual.equalTo;
|
import static org.hamcrest.core.IsEqual.equalTo;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
import org.apache.poi.hssf.HSSFTestDataSamples;
|
import org.apache.poi.hssf.HSSFTestDataSamples;
|
||||||
|
import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
|
||||||
import org.apache.poi.hssf.usermodel.HSSFSheet;
|
import org.apache.poi.hssf.usermodel.HSSFSheet;
|
||||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||||
import org.apache.poi.poifs.crypt.CryptoFunctions;
|
import org.apache.poi.poifs.crypt.CryptoFunctions;
|
@ -0,0 +1,106 @@
|
|||||||
|
/* ====================================================================
|
||||||
|
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.binaryrc4;
|
||||||
|
|
||||||
|
import static org.apache.poi.util.HexRead.readFromString;
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
|
import org.apache.poi.poifs.crypt.Decryptor;
|
||||||
|
import org.apache.poi.poifs.crypt.EncryptionInfo;
|
||||||
|
import org.apache.poi.poifs.crypt.EncryptionMode;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class TestBinaryRC4 {
|
||||||
|
@Test
|
||||||
|
public void createKeyDigest() throws GeneralSecurityException {
|
||||||
|
byte[] docIdData = readFromString("17 F6 D1 6B 09 B1 5F 7B 4C 9D 03 B4 81 B5 B4 4A");
|
||||||
|
byte[] expResult = readFromString("C2 D9 56 B2 6B");
|
||||||
|
|
||||||
|
EncryptionInfo ei = new EncryptionInfo(EncryptionMode.binaryRC4);
|
||||||
|
BinaryRC4EncryptionVerifier ver = (BinaryRC4EncryptionVerifier)ei.getVerifier();
|
||||||
|
ver.setSalt(docIdData);
|
||||||
|
SecretKey sk = BinaryRC4Decryptor.generateSecretKey("MoneyForNothing", ver);
|
||||||
|
|
||||||
|
assertArrayEquals("keyDigest mismatch", expResult, sk.getEncoded());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidateWithDefaultPassword() throws GeneralSecurityException {
|
||||||
|
|
||||||
|
String docIdSuffixA = "F 35 52 38 0D 75 4A E6 85 C2 FD 78 CE 3D D1 B6"; // valid prefix is 'D'
|
||||||
|
String saltHashA = "30 38 BE 5E 93 C5 7E B4 5F 52 CD A1 C6 8F B6 2A";
|
||||||
|
String saltDataA = "D4 04 43 EC B7 A7 6F 6A D2 68 C7 DF CF A8 80 68";
|
||||||
|
|
||||||
|
String docIdB = "39 D7 80 41 DA E4 74 2C 8C 84 F9 4D 39 9A 19 2D";
|
||||||
|
String saltDataSuffixB = "3 EA 8D 52 11 11 37 D2 BD 55 4C 01 0A 47 6E EB"; // valid prefix is 'C'
|
||||||
|
String saltHashB = "96 19 F5 D0 F1 63 08 F1 3E 09 40 1E 87 F0 4E 16";
|
||||||
|
|
||||||
|
confirmValid(true, "D" + docIdSuffixA, saltDataA, saltHashA);
|
||||||
|
confirmValid(true, docIdB, "C" + saltDataSuffixB, saltHashB);
|
||||||
|
confirmValid(false, "E" + docIdSuffixA, saltDataA, saltHashA);
|
||||||
|
confirmValid(false, docIdB, "B" + saltDataSuffixB, saltHashB);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidateWithSuppliedPassword() throws GeneralSecurityException {
|
||||||
|
|
||||||
|
String docId = "DF 35 52 38 0D 75 4A E6 85 C2 FD 78 CE 3D D1 B6";
|
||||||
|
String saltData = "D4 04 43 EC B7 A7 6F 6A D2 68 C7 DF CF A8 80 68";
|
||||||
|
String saltHashA = "8D C2 63 CC E1 1D E0 05 20 16 96 AF 48 59 94 64"; // for password '5ecret'
|
||||||
|
String saltHashB = "31 0B 0D A4 69 55 8E 27 A1 03 AD C9 AE F8 09 04"; // for password '5ecret'
|
||||||
|
|
||||||
|
confirmValid(true, docId, saltData, saltHashA, "5ecret");
|
||||||
|
confirmValid(false, docId, saltData, saltHashA, "Secret");
|
||||||
|
confirmValid(true, docId, saltData, saltHashB, "Secret");
|
||||||
|
confirmValid(false, docId, saltData, saltHashB, "secret");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void confirmValid(boolean expectedResult,
|
||||||
|
String docIdHex, String saltDataHex, String saltHashHex) throws GeneralSecurityException {
|
||||||
|
confirmValid(expectedResult, docIdHex, saltDataHex, saltHashHex, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void confirmValid(boolean expectedResult, String docIdHex,
|
||||||
|
String saltDataHex, String saltHashHex, String password) throws GeneralSecurityException {
|
||||||
|
byte[] docId = readFromString(docIdHex);
|
||||||
|
byte[] saltData = readFromString(saltDataHex);
|
||||||
|
byte[] saltHash = readFromString(saltHashHex);
|
||||||
|
|
||||||
|
EncryptionInfo ei = new EncryptionInfo(EncryptionMode.binaryRC4);
|
||||||
|
BinaryRC4EncryptionVerifier ver = (BinaryRC4EncryptionVerifier)ei.getVerifier();
|
||||||
|
ver.setSalt(docId);
|
||||||
|
ver.setEncryptedVerifier(saltData);
|
||||||
|
ver.setEncryptedVerifierHash(saltHash);
|
||||||
|
|
||||||
|
String pass = password == null ? Decryptor.DEFAULT_PASSWORD : password;
|
||||||
|
boolean actResult = ei.getDecryptor().verifyPassword(pass);
|
||||||
|
if (expectedResult) {
|
||||||
|
assertTrue("validate failed", actResult);
|
||||||
|
} else {
|
||||||
|
assertFalse("validate succeeded unexpectedly", actResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
BIN
test-data/slideshow/60003.ppt
Normal file
BIN
test-data/slideshow/60003.ppt
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user