Preparations for hssf_cryptoapi:
- Add cloneable - Change existing hslf cryptoapi to streaming git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hssf_cryptoapi@1755127 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
ab36704d3e
commit
c4ac2e7758
@ -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;
|
||||||
@ -60,6 +61,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,14 +198,19 @@ 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
|
||||||
if (dirNode == null || !dirNode.hasEntry(setName)) {
|
if (dirNode == null || !dirNode.hasEntry(setName)) {
|
||||||
|
@ -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.hssf.record.crypto.Biff8EncryptionKey;
|
||||||
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;
|
||||||
@ -91,6 +92,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 {
|
||||||
|
|
||||||
@ -123,8 +128,8 @@ public final class RecordInputStream implements LittleEndianInput {
|
|||||||
_bhi = new SimpleHeaderInput(in);
|
_bhi = new SimpleHeaderInput(in);
|
||||||
} else {
|
} else {
|
||||||
Biff8DecryptingStream bds = new Biff8DecryptingStream(in, initialOffset, key);
|
Biff8DecryptingStream bds = new Biff8DecryptingStream(in, initialOffset, key);
|
||||||
_bhi = bds;
|
|
||||||
_dataInput = bds;
|
_dataInput = bds;
|
||||||
|
_bhi = bds;
|
||||||
}
|
}
|
||||||
_nextSid = readNextSid();
|
_nextSid = readNextSid();
|
||||||
}
|
}
|
||||||
@ -491,4 +496,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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
==================================================================== */
|
==================================================================== */
|
||||||
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;
|
||||||
@ -29,54 +30,81 @@ 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 final long _size;
|
||||||
private long _pos = 0;
|
private final byte[] _chunk;
|
||||||
private long _size;
|
private final Cipher _cipher;
|
||||||
private byte[] _chunk;
|
|
||||||
private Cipher _cipher;
|
private int _lastIndex;
|
||||||
|
private long _pos;
|
||||||
|
private boolean _chunkIsValid = false;
|
||||||
|
|
||||||
public ChunkedCipherInputStream(LittleEndianInput stream, long size, int chunkSize)
|
public ChunkedCipherInputStream(LittleEndianInput stream, long size, int chunkSize)
|
||||||
|
throws GeneralSecurityException {
|
||||||
|
this(stream, size, chunkSize, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkedCipherInputStream(LittleEndianInput stream, long size, int chunkSize, int initialPos)
|
||||||
throws GeneralSecurityException {
|
throws GeneralSecurityException {
|
||||||
super((InputStream)stream);
|
super((InputStream)stream);
|
||||||
_size = size;
|
_size = size;
|
||||||
this.chunkSize = chunkSize;
|
_pos = initialPos;
|
||||||
chunkMask = chunkSize-1;
|
this._chunkSize = chunkSize;
|
||||||
chunkBits = Integer.bitCount(chunkMask);
|
if (chunkSize == -1) {
|
||||||
|
_chunk = new byte[4096];
|
||||||
|
} else {
|
||||||
|
_chunk = new byte[chunkSize];
|
||||||
|
}
|
||||||
|
_chunkBits = Integer.bitCount(_chunk.length-1);
|
||||||
|
_lastIndex = (int)(_pos >> _chunkBits);
|
||||||
|
_cipher = initCipherForBlock(null, _lastIndex);
|
||||||
|
}
|
||||||
|
|
||||||
_cipher = initCipherForBlock(null, 0);
|
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;
|
int total = 0;
|
||||||
|
|
||||||
if (available() <= 0) return -1;
|
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;
|
||||||
@ -86,8 +114,9 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
|
|||||||
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,18 +124,28 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,17 +164,37 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
|
|||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] nextChunk() throws GeneralSecurityException, IOException {
|
private int getChunkMask() {
|
||||||
int index = (int)(_pos >> chunkBits);
|
return _chunk.length-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void nextChunk() throws GeneralSecurityException, IOException {
|
||||||
|
if (_chunkSize != -1) {
|
||||||
|
int index = (int)(_pos >> _chunkBits);
|
||||||
initCipherForBlock(_cipher, index);
|
initCipherForBlock(_cipher, index);
|
||||||
|
|
||||||
if (_lastIndex != index) {
|
if (_lastIndex != index) {
|
||||||
super.skip((index - _lastIndex) << chunkBits);
|
super.skip((index - _lastIndex) << _chunkBits);
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] block = new byte[Math.min(super.available(), chunkSize)];
|
|
||||||
super.read(block, 0, block.length);
|
|
||||||
_lastIndex = index + 1;
|
_lastIndex = index + 1;
|
||||||
return _cipher.doFinal(block);
|
}
|
||||||
|
|
||||||
|
final int todo = (int)Math.min(_size, _chunk.length);
|
||||||
|
int readBytes = 0, totalBytes = 0;
|
||||||
|
do {
|
||||||
|
readBytes = super.read(_chunk, totalBytes, todo-totalBytes);
|
||||||
|
totalBytes += Math.max(0, readBytes);
|
||||||
|
} while (readBytes != -1 && totalBytes < todo);
|
||||||
|
|
||||||
|
if (readBytes == -1 && _pos+totalBytes < _size) {
|
||||||
|
throw new EOFException("buffer underrun");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_chunkSize == -1) {
|
||||||
|
_cipher.update(_chunk, 0, totalBytes, _chunk);
|
||||||
|
} else {
|
||||||
|
_cipher.doFinal(_chunk, 0, totalBytes, _chunk);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ 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 +42,177 @@ 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 _chunkSize;
|
||||||
protected final int chunkMask;
|
protected final int _chunkBits;
|
||||||
protected final int chunkBits;
|
|
||||||
|
|
||||||
private final byte[] _chunk;
|
private final byte[] _chunk;
|
||||||
private final File fileOut;
|
private final File _fileOut;
|
||||||
private final DirectoryNode dir;
|
private final DirectoryNode _dir;
|
||||||
|
|
||||||
private long _pos = 0;
|
private long _pos = 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];
|
_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);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Cipher initCipherForBlock(Cipher existing, int block, boolean lastChunk)
|
public ChunkedCipherOutputStream(OutputStream stream, int chunkSize) throws IOException, GeneralSecurityException {
|
||||||
throws GeneralSecurityException;
|
super(stream);
|
||||||
|
this._chunkSize = chunkSize;
|
||||||
|
int cs = chunkSize == STREAMING ? 4096 : chunkSize;
|
||||||
|
_chunk = new byte[cs];
|
||||||
|
_chunkBits = Integer.bitCount(cs-1);
|
||||||
|
_fileOut = null;
|
||||||
|
_dir = null;
|
||||||
|
_cipher = initCipherForBlock(null, 0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Cipher initCipherForBlock(int block, boolean lastChunk) throws IOException, GeneralSecurityException {
|
||||||
|
return initCipherForBlock(_cipher, block, lastChunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Cipher initCipherForBlock(Cipher existing, int block, boolean lastChunk)
|
||||||
|
throws 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void write(byte[] b, int off, int len)
|
public void write(byte[] b, int off, int len)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
if (len == 0) return;
|
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);
|
||||||
_pos += nextLen;
|
_pos += 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 {
|
private int getChunkMask() {
|
||||||
int posInChunk = (int)(_pos & chunkMask);
|
return _chunk.length-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void writeChunk(boolean continued) throws IOException {
|
||||||
|
if (_pos == 0) {
|
||||||
|
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 {
|
||||||
|
if (_chunkSize == STREAMING) {
|
||||||
|
if (continued) {
|
||||||
|
ciLen = _cipher.update(_chunk, 0, posInChunk, _chunk);
|
||||||
|
} else {
|
||||||
|
ciLen = _cipher.doFinal(_chunk, 0, posInChunk, _chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset stream (not only) in case we were interrupted by plain stream parts
|
||||||
|
_pos = 0;
|
||||||
|
} else {
|
||||||
|
_cipher = initCipherForBlock(_cipher, index, lastChunk);
|
||||||
|
ciLen = _cipher.doFinal(_chunk, 0, posInChunk, _chunk);
|
||||||
|
}
|
||||||
|
} 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// 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.
|
// 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
|
// 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
|
// value, depending on the block size of the chosen encryption algorithm
|
||||||
|
byte buf[] = new byte[LittleEndianConsts.LONG_SIZE];
|
||||||
LittleEndian.putLong(buf, 0, _pos);
|
LittleEndian.putLong(buf, 0, _pos);
|
||||||
os.write(buf, 0, LittleEndian.LONG_SIZE);
|
os.write(buf);
|
||||||
|
|
||||||
FileInputStream fis = new FileInputStream(fileOut);
|
FileInputStream fis = new FileInputStream(_fileOut);
|
||||||
int readBytes;
|
IOUtils.copy(fis, os);
|
||||||
while ((readBytes = fis.read(buf)) != -1) {
|
|
||||||
os.write(buf, 0, readBytes);
|
|
||||||
}
|
|
||||||
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);
|
||||||
|
@ -20,24 +20,26 @@ 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;
|
||||||
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
|
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
|
||||||
import org.apache.poi.poifs.filesystem.OPOIFSFileSystem;
|
import org.apache.poi.poifs.filesystem.OPOIFSFileSystem;
|
||||||
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
||||||
|
import org.apache.poi.util.LittleEndianInput;
|
||||||
|
|
||||||
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 +56,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(LittleEndianInput 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 +126,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 +169,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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,15 +34,15 @@ import org.apache.poi.util.LittleEndianInput;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
public class EncryptionInfo {
|
public class EncryptionInfo implements Cloneable {
|
||||||
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
|
||||||
@ -96,11 +96,10 @@ public class EncryptionInfo {
|
|||||||
|
|
||||||
public EncryptionInfo(LittleEndianInput dis, boolean isCryptoAPI) throws IOException {
|
public EncryptionInfo(LittleEndianInput dis, boolean isCryptoAPI) throws IOException {
|
||||||
final EncryptionMode encryptionMode;
|
final EncryptionMode encryptionMode;
|
||||||
versionMajor = dis.readShort();
|
versionMajor = dis.readUShort();
|
||||||
versionMinor = dis.readShort();
|
versionMinor = dis.readUShort();
|
||||||
|
|
||||||
if (!isCryptoAPI
|
if ( versionMajor == binaryRC4.versionMajor
|
||||||
&& versionMajor == binaryRC4.versionMajor
|
|
||||||
&& versionMinor == binaryRC4.versionMinor) {
|
&& versionMinor == binaryRC4.versionMinor) {
|
||||||
encryptionMode = binaryRC4;
|
encryptionMode = binaryRC4;
|
||||||
encryptionFlags = -1;
|
encryptionFlags = -1;
|
||||||
@ -138,10 +137,6 @@ public class EncryptionInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
eib.initialize(this, dis);
|
eib.initialize(this, dis);
|
||||||
header = eib.getHeader();
|
|
||||||
verifier = eib.getVerifier();
|
|
||||||
decryptor = eib.getDecryptor();
|
|
||||||
encryptor = eib.getEncryptor();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -187,11 +182,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 +219,32 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,4 +68,20 @@ public abstract class Encryptor {
|
|||||||
protected void setSecretKey(SecretKey secretKey) {
|
protected void setSecretKey(SecretKey secretKey) {
|
||||||
this.secretKey = secretKey;
|
this.secretKey = secretKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EncryptionInfo getEncryptionInfo() {
|
||||||
|
return encryptionInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEncryptionInfo(EncryptionInfo encryptionInfo) {
|
||||||
|
this.encryptionInfo = encryptionInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,36 +29,45 @@ import javax.crypto.spec.SecretKeySpec;
|
|||||||
|
|
||||||
import org.apache.poi.EncryptedDocumentException;
|
import org.apache.poi.EncryptedDocumentException;
|
||||||
import org.apache.poi.poifs.crypt.*;
|
import org.apache.poi.poifs.crypt.*;
|
||||||
|
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.util.LittleEndian;
|
import org.apache.poi.util.LittleEndian;
|
||||||
|
import org.apache.poi.util.LittleEndianInput;
|
||||||
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(LittleEndianInput 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 +87,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 +111,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 +131,22 @@ 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public InputStream getDataStream(LittleEndianInput stream)
|
||||||
|
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 +154,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,24 +34,95 @@ 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;
|
protected BinaryRC4Encryptor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void confirmPassword(String password) {
|
||||||
|
Random r = new SecureRandom();
|
||||||
|
byte salt[] = new byte[16];
|
||||||
|
byte verifier[] = new byte[16];
|
||||||
|
r.nextBytes(salt);
|
||||||
|
r.nextBytes(verifier);
|
||||||
|
confirmPassword(password, null, null, verifier, salt, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void confirmPassword(String password, byte keySpec[],
|
||||||
|
byte keySalt[], byte verifier[], byte verifierSalt[],
|
||||||
|
byte integritySalt[]) {
|
||||||
|
BinaryRC4EncryptionVerifier ver = (BinaryRC4EncryptionVerifier)getEncryptionInfo().getVerifier();
|
||||||
|
ver.setSalt(verifierSalt);
|
||||||
|
SecretKey skey = BinaryRC4Decryptor.generateSecretKey(password, ver);
|
||||||
|
setSecretKey(skey);
|
||||||
|
try {
|
||||||
|
Cipher cipher = BinaryRC4Decryptor.initCipherForBlock(null, 0, getEncryptionInfo(), skey, Cipher.ENCRYPT_MODE);
|
||||||
|
byte encryptedVerifier[] = new byte[16];
|
||||||
|
cipher.update(verifier, 0, 16, encryptedVerifier);
|
||||||
|
ver.setEncryptedVerifier(encryptedVerifier);
|
||||||
|
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
|
||||||
|
MessageDigest hashAlg = CryptoFunctions.getMessageDigest(hashAlgo);
|
||||||
|
byte calcVerifierHash[] = hashAlg.digest(verifier);
|
||||||
|
byte encryptedVerifierHash[] = cipher.doFinal(calcVerifierHash);
|
||||||
|
ver.setEncryptedVerifierHash(encryptedVerifierHash);
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
throw new EncryptedDocumentException("Password confirmation failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream getDataStream(DirectoryNode dir)
|
||||||
|
throws IOException, GeneralSecurityException {
|
||||||
|
OutputStream countStream = new BinaryRC4CipherOutputStream(dir);
|
||||||
|
return countStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getKeySizeInBytes() {
|
||||||
|
return getEncryptionInfo().getHeader().getKeySize() / 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void createEncryptionInfoEntry(DirectoryNode dir) throws IOException {
|
||||||
|
DataSpaceMapUtils.addDefaultDataSpace(dir);
|
||||||
|
final EncryptionInfo info = getEncryptionInfo();
|
||||||
|
final BinaryRC4EncryptionHeader header = (BinaryRC4EncryptionHeader)info.getHeader();
|
||||||
|
final BinaryRC4EncryptionVerifier verifier = (BinaryRC4EncryptionVerifier)info.getVerifier();
|
||||||
|
EncryptionRecord er = new EncryptionRecord() {
|
||||||
|
@Override
|
||||||
|
public void write(LittleEndianByteArrayOutputStream bos) {
|
||||||
|
bos.writeShort(info.getVersionMajor());
|
||||||
|
bos.writeShort(info.getVersionMinor());
|
||||||
|
header.write(bos);
|
||||||
|
verifier.write(bos);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
DataSpaceMapUtils.createEncryptionEntry(dir, "EncryptionInfo", er);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BinaryRC4Encryptor clone() throws CloneNotSupportedException {
|
||||||
|
return (BinaryRC4Encryptor)super.clone();
|
||||||
|
}
|
||||||
|
|
||||||
protected class BinaryRC4CipherOutputStream extends ChunkedCipherOutputStream {
|
protected class BinaryRC4CipherOutputStream extends ChunkedCipherOutputStream {
|
||||||
|
|
||||||
|
@Override
|
||||||
protected Cipher initCipherForBlock(Cipher cipher, int block, boolean lastChunk)
|
protected Cipher initCipherForBlock(Cipher cipher, int block, boolean lastChunk)
|
||||||
throws GeneralSecurityException {
|
throws GeneralSecurityException {
|
||||||
return BinaryRC4Decryptor.initCipherForBlock(cipher, block, builder, getSecretKey(), Cipher.ENCRYPT_MODE);
|
return BinaryRC4Decryptor.initCipherForBlock(cipher, block, getEncryptionInfo(), getSecretKey(), Cipher.ENCRYPT_MODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected void calculateChecksum(File file, int i) {
|
protected void calculateChecksum(File file, int i) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected void createEncryptionInfoEntry(DirectoryNode dir, File tmpFile)
|
protected void createEncryptionInfoEntry(DirectoryNode dir, File tmpFile)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
BinaryRC4Encryptor.this.createEncryptionInfoEntry(dir);
|
BinaryRC4Encryptor.this.createEncryptionInfoEntry(dir);
|
||||||
@ -62,66 +133,4 @@ public class BinaryRC4Encryptor extends Encryptor {
|
|||||||
super(dir, 512);
|
super(dir, 512);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected BinaryRC4Encryptor(BinaryRC4EncryptionInfoBuilder builder) {
|
|
||||||
this.builder = builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void confirmPassword(String password) {
|
|
||||||
Random r = new SecureRandom();
|
|
||||||
byte salt[] = new byte[16];
|
|
||||||
byte verifier[] = new byte[16];
|
|
||||||
r.nextBytes(salt);
|
|
||||||
r.nextBytes(verifier);
|
|
||||||
confirmPassword(password, null, null, verifier, salt, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void confirmPassword(String password, byte keySpec[],
|
|
||||||
byte keySalt[], byte verifier[], byte verifierSalt[],
|
|
||||||
byte integritySalt[]) {
|
|
||||||
BinaryRC4EncryptionVerifier ver = builder.getVerifier();
|
|
||||||
ver.setSalt(verifierSalt);
|
|
||||||
SecretKey skey = BinaryRC4Decryptor.generateSecretKey(password, ver);
|
|
||||||
setSecretKey(skey);
|
|
||||||
try {
|
|
||||||
Cipher cipher = BinaryRC4Decryptor.initCipherForBlock(null, 0, builder, skey, Cipher.ENCRYPT_MODE);
|
|
||||||
byte encryptedVerifier[] = new byte[16];
|
|
||||||
cipher.update(verifier, 0, 16, encryptedVerifier);
|
|
||||||
ver.setEncryptedVerifier(encryptedVerifier);
|
|
||||||
org.apache.poi.poifs.crypt.HashAlgorithm hashAlgo = ver
|
|
||||||
.getHashAlgorithm();
|
|
||||||
MessageDigest hashAlg = CryptoFunctions.getMessageDigest(hashAlgo);
|
|
||||||
byte calcVerifierHash[] = hashAlg.digest(verifier);
|
|
||||||
byte encryptedVerifierHash[] = cipher.doFinal(calcVerifierHash);
|
|
||||||
ver.setEncryptedVerifierHash(encryptedVerifierHash);
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
throw new EncryptedDocumentException("Password confirmation failed", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public OutputStream getDataStream(DirectoryNode dir)
|
|
||||||
throws IOException, GeneralSecurityException {
|
|
||||||
OutputStream countStream = new BinaryRC4CipherOutputStream(dir);
|
|
||||||
return countStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected int getKeySizeInBytes() {
|
|
||||||
return builder.getHeader().getKeySize() / 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void createEncryptionInfoEntry(DirectoryNode dir) throws IOException {
|
|
||||||
DataSpaceMapUtils.addDefaultDataSpace(dir);
|
|
||||||
final EncryptionInfo info = builder.getEncryptionInfo();
|
|
||||||
final BinaryRC4EncryptionHeader header = builder.getHeader();
|
|
||||||
final BinaryRC4EncryptionVerifier verifier = builder.getVerifier();
|
|
||||||
EncryptionRecord er = new EncryptionRecord() {
|
|
||||||
public void write(LittleEndianByteArrayOutputStream bos) {
|
|
||||||
bos.writeShort(info.getVersionMajor());
|
|
||||||
bos.writeShort(info.getVersionMinor());
|
|
||||||
header.write(bos);
|
|
||||||
verifier.write(bos);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
DataSpaceMapUtils.createEncryptionEntry(dir, "EncryptionInfo", er);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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,78 +26,33 @@ 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;
|
||||||
import org.apache.poi.util.IOUtils;
|
import org.apache.poi.util.IOUtils;
|
||||||
import org.apache.poi.util.LittleEndian;
|
import org.apache.poi.util.LittleEndian;
|
||||||
|
import org.apache.poi.util.LittleEndianInput;
|
||||||
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 +65,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 +94,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 +139,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(LittleEndianInput 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 +158,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 +202,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(LittleEndianInput 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;
|
|
||||||
|
|
||||||
protected CryptoAPIEncryptor(CryptoAPIEncryptionInfoBuilder builder) {
|
private int _chunkSize = 512;
|
||||||
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,7 +102,18 @@ 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
public CryptoAPICipherOutputStream getDataStream(OutputStream stream)
|
||||||
|
throws IOException, GeneralSecurityException {
|
||||||
|
return new CryptoAPICipherOutputStream(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -109,9 +123,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 +138,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 +209,20 @@ public class CryptoAPIEncryptor extends Encryptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected int getKeySizeInBytes() {
|
protected int getKeySizeInBytes() {
|
||||||
return builder.getHeader().getKeySize() / 8;
|
return getEncryptionInfo().getHeader().getKeySize() / 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 +233,42 @@ 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 {
|
@Override
|
||||||
setBlock(0);
|
public CryptoAPIEncryptor clone() throws CloneNotSupportedException {
|
||||||
|
return (CryptoAPIEncryptor)super.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getBuf() {
|
protected class CryptoAPICipherOutputStream extends ChunkedCipherOutputStream {
|
||||||
return buf;
|
|
||||||
|
@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);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSize(int count) {
|
@Override
|
||||||
this.count = count;
|
protected void calculateChecksum(File file, int i) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBlock(int block) throws GeneralSecurityException {
|
@Override
|
||||||
cipher = initCipherForBlock(cipher, block);
|
protected void createEncryptionInfoEntry(DirectoryNode dir, File tmpFile)
|
||||||
|
throws IOException, GeneralSecurityException {
|
||||||
|
throw new RuntimeException("createEncryptionInfoEntry not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void write(int b) {
|
public CryptoAPICipherOutputStream(OutputStream stream)
|
||||||
try {
|
throws IOException, GeneralSecurityException {
|
||||||
oneByte[0] = (byte)b;
|
super(stream, CryptoAPIEncryptor.this._chunkSize);
|
||||||
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) {
|
@Override
|
||||||
try {
|
public void flush() throws IOException {
|
||||||
cipher.update(b, off, len, b, off);
|
writeChunk(false);
|
||||||
super.write(b, off, len);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new EncryptedDocumentException(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -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
|
||||||
|
if (is instanceof RecordInputStream) {
|
||||||
|
((RecordInputStream)is).mark(LittleEndianConsts.INT_SIZE+1);
|
||||||
|
} else {
|
||||||
((InputStream)is).mark(LittleEndianConsts.INT_SIZE+1);
|
((InputStream)is).mark(LittleEndianConsts.INT_SIZE+1);
|
||||||
|
}
|
||||||
int checkForSalt = is.readInt();
|
int checkForSalt = is.readInt();
|
||||||
|
if (is instanceof RecordInputStream) {
|
||||||
|
((RecordInputStream)is).reset();
|
||||||
|
} else {
|
||||||
((InputStream)is).reset();
|
((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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,33 +28,28 @@ 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,103 +17,91 @@
|
|||||||
|
|
||||||
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) {
|
|
||||||
this(buf, startOffset, buf.length - startOffset);
|
|
||||||
}
|
|
||||||
public LittleEndianByteArrayInputStream(byte[] buf) {
|
|
||||||
this(buf, 0, buf.length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int available() {
|
public LittleEndianByteArrayInputStream(byte[] buf, int startOffset) {
|
||||||
return _endIndex - _readIndex;
|
super(buf, startOffset, buf.length - startOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LittleEndianByteArrayInputStream(byte[] buf) {
|
||||||
|
super(buf);
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public byte readByte() {
|
public byte readByte() {
|
||||||
checkPosition(1);
|
checkPosition(1);
|
||||||
return _buf[_readIndex++];
|
return (byte)read();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int readInt() {
|
public int readInt() {
|
||||||
checkPosition(4);
|
final int size = LittleEndianConsts.INT_SIZE;
|
||||||
int i = _readIndex;
|
checkPosition(size);
|
||||||
|
int le = LittleEndian.getInt(buf, pos);
|
||||||
int b0 = _buf[i++] & 0xFF;
|
super.skip(size);
|
||||||
int b1 = _buf[i++] & 0xFF;
|
return le;
|
||||||
int b2 = _buf[i++] & 0xFF;
|
|
||||||
int b3 = _buf[i++] & 0xFF;
|
|
||||||
_readIndex = i;
|
|
||||||
return (b3 << 24) + (b2 << 16) + (b1 << 8) + (b0 << 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public long readLong() {
|
public long readLong() {
|
||||||
checkPosition(8);
|
final int size = LittleEndianConsts.LONG_SIZE;
|
||||||
int i = _readIndex;
|
checkPosition(size);
|
||||||
|
long le = LittleEndian.getLong(buf, pos);
|
||||||
int b0 = _buf[i++] & 0xFF;
|
super.skip(size);
|
||||||
int b1 = _buf[i++] & 0xFF;
|
return le;
|
||||||
int b2 = _buf[i++] & 0xFF;
|
|
||||||
int b3 = _buf[i++] & 0xFF;
|
|
||||||
int b4 = _buf[i++] & 0xFF;
|
|
||||||
int b5 = _buf[i++] & 0xFF;
|
|
||||||
int b6 = _buf[i++] & 0xFF;
|
|
||||||
int b7 = _buf[i++] & 0xFF;
|
|
||||||
_readIndex = i;
|
|
||||||
return (((long)b7 << 56) +
|
|
||||||
((long)b6 << 48) +
|
|
||||||
((long)b5 << 40) +
|
|
||||||
((long)b4 << 32) +
|
|
||||||
((long)b3 << 24) +
|
|
||||||
(b2 << 16) +
|
|
||||||
(b1 << 8) +
|
|
||||||
(b0 << 0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public short readShort() {
|
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) {
|
|
||||||
checkPosition(len);
|
@Override
|
||||||
System.arraycopy(_buf, _readIndex, buf, off, len);
|
public int readUShort() {
|
||||||
_readIndex+=len;
|
final int size = LittleEndianConsts.SHORT_SIZE;
|
||||||
}
|
checkPosition(size);
|
||||||
public void readFully(byte[] buf) {
|
int le = LittleEndian.getUShort(buf, pos);
|
||||||
readFully(buf, 0, buf.length);
|
super.skip(size);
|
||||||
|
return le;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public double readDouble() {
|
public double readDouble() {
|
||||||
return Double.longBitsToDouble(readLong());
|
return Double.longBitsToDouble(readLong());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readFully(byte[] buffer, int off, int len) {
|
||||||
|
checkPosition(len);
|
||||||
|
read(buffer, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readFully(byte[] buffer) {
|
||||||
|
checkPosition(buffer.length);
|
||||||
|
read(buffer, 0, buffer.length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,14 +17,12 @@
|
|||||||
|
|
||||||
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;
|
||||||
@ -52,15 +50,18 @@ public final class LittleEndianByteArrayOutputStream implements LittleEndianOutp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void writeByte(int v) {
|
public void writeByte(int v) {
|
||||||
checkPosition(1);
|
checkPosition(1);
|
||||||
_buf[_writeIndex++] = (byte)v;
|
_buf[_writeIndex++] = (byte)v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void writeDouble(double v) {
|
public void writeDouble(double v) {
|
||||||
writeLong(Double.doubleToLongBits(v));
|
writeLong(Double.doubleToLongBits(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void writeInt(int v) {
|
public void writeInt(int v) {
|
||||||
checkPosition(4);
|
checkPosition(4);
|
||||||
int i = _writeIndex;
|
int i = _writeIndex;
|
||||||
@ -71,11 +72,13 @@ public final class LittleEndianByteArrayOutputStream implements LittleEndianOutp
|
|||||||
_writeIndex = i;
|
_writeIndex = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void writeLong(long v) {
|
public void writeLong(long v) {
|
||||||
writeInt((int)(v >> 0));
|
writeInt((int)(v >> 0));
|
||||||
writeInt((int)(v >> 32));
|
writeInt((int)(v >> 32));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void writeShort(int v) {
|
public void writeShort(int v) {
|
||||||
checkPosition(2);
|
checkPosition(2);
|
||||||
int i = _writeIndex;
|
int i = _writeIndex;
|
||||||
@ -83,20 +86,32 @@ public final class LittleEndianByteArrayOutputStream implements LittleEndianOutp
|
|||||||
_buf[i++] = (byte)((v >>> 8) & 0xFF);
|
_buf[i++] = (byte)((v >>> 8) & 0xFF);
|
||||||
_writeIndex = i;
|
_writeIndex = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int b) {
|
||||||
|
writeByte(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void write(byte[] b) {
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void write(byte[] b, int offset, int len) {
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public LittleEndianOutput createDelayedOutput(int size) {
|
public LittleEndianOutput createDelayedOutput(int size) {
|
||||||
checkPosition(size);
|
checkPosition(size);
|
||||||
LittleEndianOutput result = new LittleEndianByteArrayOutputStream(_buf, _writeIndex, size);
|
LittleEndianOutput result = new LittleEndianByteArrayOutputStream(_buf, _writeIndex, size);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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[]) {
|
||||||
AgileEncryptionVerifier ver = builder.getVerifier();
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,27 +35,46 @@ 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;
|
||||||
@ -111,27 +131,18 @@ public class HSLFSlideShowEncrypted {
|
|||||||
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();
|
||||||
}
|
}
|
||||||
@ -144,43 +155,89 @@ public class HSLFSlideShowEncrypted {
|
|||||||
|| record instanceof PersistPtrHolder
|
|| record instanceof PersistPtrHolder
|
||||||
|| record instanceof DocumentEncryptionAtom
|
|| record instanceof DocumentEncryptionAtom
|
||||||
);
|
);
|
||||||
if (isPlain) return plainStream;
|
|
||||||
|
try {
|
||||||
|
if (isPlain) {
|
||||||
|
if (cyos != null) {
|
||||||
|
// write cached data to stream
|
||||||
|
cyos.flush();
|
||||||
|
}
|
||||||
|
return plainStream;
|
||||||
|
}
|
||||||
|
|
||||||
encryptInit();
|
encryptInit();
|
||||||
setPersistId(persistId);
|
|
||||||
|
|
||||||
if (cyos == null) {
|
if (cyos == null) {
|
||||||
cyos = new CipherOutputStream(plainStream, cipher);
|
enc.setChunkSize(-1);
|
||||||
|
cyos = enc.getDataStream(plainStream);
|
||||||
|
}
|
||||||
|
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);
|
||||||
@ -192,37 +249,25 @@ public class HSLFSlideShowEncrypted {
|
|||||||
// 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);
|
||||||
@ -232,37 +277,50 @@ 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);
|
||||||
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;
|
||||||
|
|
||||||
@ -271,30 +329,20 @@ public class HSLFSlideShowEncrypted {
|
|||||||
// 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,8 +351,8 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,22 +360,35 @@ public class HSLFSlideShowEncrypted {
|
|||||||
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -394,7 +455,9 @@ 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;
|
||||||
}
|
}
|
||||||
@ -446,13 +509,17 @@ public class HSLFSlideShowEncrypted {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
@ -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) {
|
||||||
@ -490,11 +561,20 @@ public class HSLFSlideShowEncrypted {
|
|||||||
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,13 +206,11 @@ 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");
|
||||||
int readLen = is.read(_docstream);
|
try {
|
||||||
|
_docstream = IOUtils.toByteArray(is, len);
|
||||||
|
} finally {
|
||||||
is.close();
|
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();
|
|
||||||
byte[] pictstream = new byte[len];
|
|
||||||
DocumentInputStream is = directory.createDocumentInputStream(entry);
|
DocumentInputStream is = directory.createDocumentInputStream(entry);
|
||||||
int readLen = is.read(pictstream);
|
byte[] pictstream = IOUtils.toByteArray(is, entry.getSize());
|
||||||
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)) {
|
||||||
@ -534,6 +526,8 @@ public final class HSLFSlideShowImpl extends POIDocument implements Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
Integer newLastUserEditAtomPos = oldToNewPositions.get(oldLastUserEditAtomPos);
|
Integer newLastUserEditAtomPos = oldToNewPositions.get(oldLastUserEditAtomPos);
|
||||||
@ -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) {
|
||||||
|
@ -25,6 +25,7 @@ import static org.junit.Assert.fail;
|
|||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -44,6 +45,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 +178,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());
|
||||||
|
Loading…
Reference in New Issue
Block a user