aboutsummaryrefslogtreecommitdiffstats
path: root/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java
diff options
context:
space:
mode:
authorAndreas Beeker <kiwiwings@apache.org>2016-08-08 00:10:44 +0000
committerAndreas Beeker <kiwiwings@apache.org>2016-08-08 00:10:44 +0000
commit075e2bfce2f6c881820c7ec2d069d5331a2b6c9b (patch)
treeb1b775c4bd5c3930de0de69670a36fc5e88247b0 /src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java
parente84c6152dde07a48b2982f9e9ce016529548e11d (diff)
downloadpoi-075e2bfce2f6c881820c7ec2d069d5331a2b6c9b.tar.gz
poi-075e2bfce2f6c881820c7ec2d069d5331a2b6c9b.zip
HSSF CryptoAPI decryption support
git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hssf_cryptoapi@1755461 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java')
-rw-r--r--src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java123
1 files changed, 96 insertions, 27 deletions
diff --git a/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java b/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java
index 255494d6ab..7b5632dea7 100644
--- a/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java
+++ b/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java
@@ -21,56 +21,56 @@ import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
+import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.ShortBufferException;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.util.Internal;
-import org.apache.poi.util.LittleEndianInput;
import org.apache.poi.util.LittleEndianInputStream;
@Internal
public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
private final int _chunkSize;
private final int _chunkBits;
-
+
private final long _size;
- private final byte[] _chunk;
+ private final byte[] _chunk, _plain;
private final Cipher _cipher;
private int _lastIndex;
private long _pos;
private boolean _chunkIsValid = false;
- public ChunkedCipherInputStream(LittleEndianInput stream, long size, int chunkSize)
+ public ChunkedCipherInputStream(InputStream stream, long size, int chunkSize)
throws GeneralSecurityException {
this(stream, size, chunkSize, 0);
}
- public ChunkedCipherInputStream(LittleEndianInput stream, long size, int chunkSize, int initialPos)
+ public ChunkedCipherInputStream(InputStream stream, long size, int chunkSize, int initialPos)
throws GeneralSecurityException {
- super((InputStream)stream);
+ super(stream);
_size = size;
_pos = initialPos;
this._chunkSize = chunkSize;
- if (chunkSize == -1) {
- _chunk = new byte[4096];
- } else {
- _chunk = new byte[chunkSize];
- }
+ int cs = chunkSize == -1 ? 4096 : chunkSize;
+ _chunk = new byte[cs];
+ _plain = new byte[cs];
_chunkBits = Integer.bitCount(_chunk.length-1);
_lastIndex = (int)(_pos >> _chunkBits);
_cipher = initCipherForBlock(null, _lastIndex);
}
-
+
public final Cipher initCipherForBlock(int block) throws IOException, GeneralSecurityException {
if (_chunkSize != -1) {
throw new GeneralSecurityException("the cipher block can only be set for streaming encryption, e.g. CryptoAPI...");
}
-
+
_chunkIsValid = false;
return initCipherForBlock(_cipher, block);
}
-
+
protected abstract Cipher initCipherForBlock(Cipher existing, int block)
throws GeneralSecurityException;
@@ -88,8 +88,12 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
@Override
public int read(byte[] b, int off, int len) throws IOException {
+ return read(b, off, len, false);
+ }
+
+ private int read(byte[] b, int off, int len, boolean readPlain) throws IOException {
int total = 0;
-
+
if (available() <= 0) {
return -1;
}
@@ -110,7 +114,9 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
return total;
}
count = Math.min(avail, Math.min(count, len));
- System.arraycopy(_chunk, (int)(_pos & chunkMask), b, off, count);
+
+ System.arraycopy(readPlain ? _plain : _chunk, (int)(_pos & chunkMask), b, off, count);
+
off += count;
len -= count;
_pos += count;
@@ -139,7 +145,7 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
public int available() {
return remainingBytes();
}
-
+
/**
* Helper method for forbidden available call - we know the size beforehand, so it's ok ...
*
@@ -148,7 +154,7 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
private int remainingBytes() {
return (int)(_size - _pos);
}
-
+
@Override
public boolean markSupported() {
return false;
@@ -158,21 +164,21 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
public synchronized void mark(int readlimit) {
throw new UnsupportedOperationException();
}
-
+
@Override
public synchronized void reset() throws IOException {
throw new UnsupportedOperationException();
}
- private int getChunkMask() {
+ protected int getChunkMask() {
return _chunk.length-1;
}
-
+
private void nextChunk() throws GeneralSecurityException, IOException {
if (_chunkSize != -1) {
int index = (int)(_pos >> _chunkBits);
initCipherForBlock(_cipher, index);
-
+
if (_lastIndex != index) {
super.skip((index - _lastIndex) << _chunkBits);
}
@@ -183,18 +189,81 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
final int todo = (int)Math.min(_size, _chunk.length);
int readBytes = 0, totalBytes = 0;
do {
- readBytes = super.read(_chunk, totalBytes, todo-totalBytes);
+ readBytes = super.read(_plain, totalBytes, todo-totalBytes);
totalBytes += Math.max(0, readBytes);
} while (readBytes != -1 && totalBytes < todo);
- if (readBytes == -1 && _pos+totalBytes < _size) {
+ if (readBytes == -1 && _pos+totalBytes < _size && _size < Integer.MAX_VALUE) {
throw new EOFException("buffer underrun");
}
- if (_chunkSize == -1) {
- _cipher.update(_chunk, 0, totalBytes, _chunk);
+ System.arraycopy(_plain, 0, _chunk, 0, totalBytes);
+
+ invokeCipher(totalBytes, _chunkSize > -1);
+ }
+
+ /**
+ * Helper function for overriding the cipher invocation, i.e. XOR doesn't use a cipher
+ * and uses it's own implementation
+ *
+ * @return
+ * @throws BadPaddingException
+ * @throws IllegalBlockSizeException
+ * @throws ShortBufferException
+ */
+ protected int invokeCipher(int totalBytes, boolean doFinal) throws GeneralSecurityException {
+ if (doFinal) {
+ return _cipher.doFinal(_chunk, 0, totalBytes, _chunk);
} else {
- _cipher.doFinal(_chunk, 0, totalBytes, _chunk);
+ return _cipher.update(_chunk, 0, totalBytes, _chunk);
+ }
+ }
+
+ /**
+ * Used when BIFF header fields (sid, size) are being read. The internal
+ * {@link Cipher} instance must step even when unencrypted bytes are read
+ */
+ public int readPlain(byte b[], int off, int len) throws IOException {
+ if (len <= 0) {
+ return len;
}
+
+ int readBytes, total = 0;
+ do {
+ readBytes = read(b, off, len, true);
+ total += Math.max(0, readBytes);
+ } while (readBytes > -1 && total < len);
+
+ return total;
+ }
+
+ /**
+ * Some ciphers (actually just XOR) are based on the record size,
+ * which needs to be set before encryption
+ *
+ * @param recordSize the size of the next record
+ */
+ public void setNextRecordSize(int recordSize) {
+ }
+
+ /**
+ * @return the chunk bytes
+ */
+ protected byte[] getChunk() {
+ return _chunk;
+ }
+
+ /**
+ * @return the plain bytes
+ */
+ protected byte[] getPlain() {
+ return _plain;
+ }
+
+ /**
+ * @return the absolute position in the stream
+ */
+ public long getPos() {
+ return _pos;
}
}