diff --git a/src/java/org/apache/poi/POIDocument.java b/src/java/org/apache/poi/POIDocument.java index 87c6a14f66..d742c5572a 100644 --- a/src/java/org/apache/poi/POIDocument.java +++ b/src/java/org/apache/poi/POIDocument.java @@ -32,6 +32,7 @@ import org.apache.poi.hpsf.PropertySet; import org.apache.poi.hpsf.PropertySetFactory; import org.apache.poi.hpsf.SummaryInformation; import org.apache.poi.poifs.crypt.EncryptionInfo; +import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIDecryptor; import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.DocumentInputStream; import org.apache.poi.poifs.filesystem.NPOIFSFileSystem; @@ -59,6 +60,8 @@ public abstract class POIDocument implements Closeable { /* Have the property streams been read yet? (Only done on-demand) */ private boolean initialized = false; + + private static final String[] encryptedStreamNames = { "EncryptedSummary" }; /** * Constructs a POIDocument with the given directory node. @@ -195,13 +198,18 @@ public abstract class POIDocument implements Closeable { try { if (encryptionInfo != null) { step = "getting encrypted"; - InputStream is = encryptionInfo.getDecryptor().getDataStream(directory); - try { - encPoifs = new NPOIFSFileSystem(is); - dirNode = encPoifs.getRoot(); - } finally { - is.close(); + String encryptedStream = null; + for (String s : encryptedStreamNames) { + if (dirNode.hasEntry(s)) { + encryptedStream = s; + } } + if (encryptedStream == null) { + throw new EncryptedDocumentException("can't find matching encrypted property stream"); + } + CryptoAPIDecryptor dec = (CryptoAPIDecryptor)encryptionInfo.getDecryptor(); + encPoifs = dec.getSummaryEntries(dirNode, encryptedStream); + dirNode = encPoifs.getRoot(); } //directory can be null when creating new documents diff --git a/src/java/org/apache/poi/hssf/record/RecordInputStream.java b/src/java/org/apache/poi/hssf/record/RecordInputStream.java index f9212c7986..8991eb7705 100644 --- a/src/java/org/apache/poi/hssf/record/RecordInputStream.java +++ b/src/java/org/apache/poi/hssf/record/RecordInputStream.java @@ -18,13 +18,14 @@ package org.apache.poi.hssf.record; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.InputStream; import java.util.Locale; import org.apache.poi.hssf.dev.BiffViewer; import org.apache.poi.hssf.record.crypto.Biff8DecryptingStream; import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey; -import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.Internal; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInput; 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 */ private int _currentDataOffset; + /** + * index within the data section when mark() was called + */ + private int _markedDataOffset; private static final class SimpleHeaderInput implements BiffHeaderInput { @@ -123,8 +128,8 @@ public final class RecordInputStream implements LittleEndianInput { _bhi = new SimpleHeaderInput(in); } else { Biff8DecryptingStream bds = new Biff8DecryptingStream(in, initialOffset, key); + _dataInput = bds; _bhi = bds; - _dataInput = bds; } _nextSid = readNextSid(); } @@ -491,4 +496,31 @@ public final class RecordInputStream implements LittleEndianInput { public int getNextSid() { return _nextSid; } + + /** + * Mark the stream position - experimental function + * + * @param readlimit the read ahead limit + * + * @see InputStream#mark(int) + */ + @Internal + public void mark(int readlimit) { + ((InputStream)_dataInput).mark(readlimit); + _markedDataOffset = _currentDataOffset; + } + + /** + * Resets the stream position to the previously marked position. + * Experimental function - this only works, when nextRecord() wasn't called in the meantime. + * + * @throws IOException if marking is not supported + * + * @see InputStream#reset() + */ + @Internal + public void reset() throws IOException { + ((InputStream)_dataInput).reset(); + _currentDataOffset = _markedDataOffset; + } } diff --git a/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java b/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java index 7d695a1eb3..255494d6ab 100644 --- a/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java +++ b/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java @@ -16,6 +16,7 @@ ==================================================================== */ package org.apache.poi.poifs.crypt; +import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.security.GeneralSecurityException; @@ -29,54 +30,81 @@ import org.apache.poi.util.LittleEndianInputStream; @Internal public abstract class ChunkedCipherInputStream extends LittleEndianInputStream { - private final int chunkSize; - private final int chunkMask; - private final int chunkBits; + private final int _chunkSize; + private final int _chunkBits; - private int _lastIndex = 0; - private long _pos = 0; - private long _size; - private byte[] _chunk; - private Cipher _cipher; + private final long _size; + private final byte[] _chunk; + private final Cipher _cipher; + + private int _lastIndex; + private long _pos; + private boolean _chunkIsValid = false; public ChunkedCipherInputStream(LittleEndianInput stream, long size, int chunkSize) - throws GeneralSecurityException { + throws GeneralSecurityException { + this(stream, size, chunkSize, 0); + } + + public ChunkedCipherInputStream(LittleEndianInput stream, long size, int chunkSize, int initialPos) + throws GeneralSecurityException { super((InputStream)stream); _size = size; - this.chunkSize = chunkSize; - chunkMask = chunkSize-1; - chunkBits = Integer.bitCount(chunkMask); + _pos = initialPos; + this._chunkSize = chunkSize; + 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); + } + + 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..."); + } - _cipher = initCipherForBlock(null, 0); + _chunkIsValid = false; + return initCipherForBlock(_cipher, block); } protected abstract Cipher initCipherForBlock(Cipher existing, int block) throws GeneralSecurityException; + @Override public int read() throws IOException { byte[] b = new byte[1]; - if (read(b) == 1) + if (read(b) == 1) { return b[0]; + } return -1; } // do not implement! -> recursion // public int read(byte[] b) throws IOException; + @Override public int read(byte[] b, int off, int len) throws IOException { int total = 0; - if (available() <= 0) return -1; + if (available() <= 0) { + return -1; + } + final int chunkMask = getChunkMask(); while (len > 0) { - if (_chunk == null) { + if (!_chunkIsValid) { try { - _chunk = nextChunk(); + nextChunk(); + _chunkIsValid = true; } catch (GeneralSecurityException e) { throw new EncryptedDocumentException(e.getMessage(), e); } } - int count = (int)(chunkSize - (_pos & chunkMask)); + int count = (int)(_chunk.length - (_pos & chunkMask)); int avail = available(); if (avail == 0) { return total; @@ -86,8 +114,9 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream { off += count; len -= count; _pos += count; - if ((_pos & chunkMask) == 0) - _chunk = null; + if ((_pos & chunkMask) == 0) { + _chunkIsValid = false; + } total += count; } @@ -95,18 +124,28 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream { } @Override - public long skip(long n) throws IOException { + public long skip(final long n) throws IOException { long start = _pos; - long skip = Math.min(available(), n); + long skip = Math.min(remainingBytes(), n); - if ((((_pos + skip) ^ start) & ~chunkMask) != 0) - _chunk = null; + if ((((_pos + skip) ^ start) & ~getChunkMask()) != 0) { + _chunkIsValid = false; + } _pos += skip; return skip; } @Override public int available() { + return remainingBytes(); + } + + /** + * Helper method for forbidden available call - we know the size beforehand, so it's ok ... + * + * @return the remaining byte until EOF + */ + private int remainingBytes() { return (int)(_size - _pos); } @@ -125,17 +164,37 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream { throw new UnsupportedOperationException(); } - private byte[] nextChunk() throws GeneralSecurityException, IOException { - int index = (int)(_pos >> chunkBits); - initCipherForBlock(_cipher, index); + private 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); + if (_lastIndex != index) { + super.skip((index - _lastIndex) << _chunkBits); + } + + _lastIndex = index + 1; } - byte[] block = new byte[Math.min(super.available(), chunkSize)]; - super.read(block, 0, block.length); - _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); + } } } diff --git a/src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java b/src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java index f0b14bffce..573cbdeb60 100644 --- a/src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java +++ b/src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java @@ -32,6 +32,7 @@ import org.apache.poi.EncryptedDocumentException; import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.POIFSWriterEvent; import org.apache.poi.poifs.filesystem.POIFSWriterListener; +import org.apache.poi.util.IOUtils; import org.apache.poi.util.Internal; import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndianConsts; @@ -41,137 +42,177 @@ import org.apache.poi.util.TempFile; @Internal public abstract class ChunkedCipherOutputStream extends FilterOutputStream { - private static final POILogger logger = POILogFactory.getLogger(ChunkedCipherOutputStream.class); - - protected final int chunkSize; - protected final int chunkMask; - protected final int chunkBits; - + private static final POILogger LOG = POILogFactory.getLogger(ChunkedCipherOutputStream.class); + private static final int STREAMING = -1; + + protected final int _chunkSize; + protected final int _chunkBits; + private final byte[] _chunk; - private final File fileOut; - private final DirectoryNode dir; + private final File _fileOut; + private final DirectoryNode _dir; private long _pos = 0; private Cipher _cipher; - + public ChunkedCipherOutputStream(DirectoryNode dir, int chunkSize) throws IOException, GeneralSecurityException { super(null); - this.chunkSize = chunkSize; - chunkMask = chunkSize-1; - chunkBits = Integer.bitCount(chunkMask); - _chunk = new byte[chunkSize]; - - fileOut = TempFile.createTempFile("encrypted_package", "crypt"); - fileOut.deleteOnExit(); - this.out = new FileOutputStream(fileOut); - this.dir = dir; + this._chunkSize = chunkSize; + int cs = chunkSize == STREAMING ? 4096 : chunkSize; + _chunk = new byte[cs]; + _chunkBits = Integer.bitCount(cs-1); + _fileOut = TempFile.createTempFile("encrypted_package", "crypt"); + _fileOut.deleteOnExit(); + this.out = new FileOutputStream(_fileOut); + this._dir = dir; _cipher = initCipherForBlock(null, 0, false); } + public ChunkedCipherOutputStream(OutputStream stream, int chunkSize) throws IOException, GeneralSecurityException { + super(stream); + this._chunkSize = chunkSize; + int cs = chunkSize == STREAMING ? 4096 : chunkSize; + _chunk = new byte[cs]; + _chunkBits = Integer.bitCount(cs-1); + _fileOut = null; + _dir = null; + _cipher = initCipherForBlock(null, 0, false); + } + + public final Cipher initCipherForBlock(int block, boolean lastChunk) throws IOException, GeneralSecurityException { + return initCipherForBlock(_cipher, block, lastChunk); + } + protected abstract Cipher initCipherForBlock(Cipher existing, int block, boolean lastChunk) - throws GeneralSecurityException; - - @SuppressWarnings("hiding") + throws IOException, GeneralSecurityException; + protected abstract void calculateChecksum(File fileOut, int oleStreamSize) throws GeneralSecurityException, IOException; - - @SuppressWarnings("hiding") + protected abstract void createEncryptionInfoEntry(DirectoryNode dir, File tmpFile) throws IOException, GeneralSecurityException; + @Override public void write(int b) throws IOException { write(new byte[]{(byte)b}); } + @Override public void write(byte[] b) throws IOException { write(b, 0, b.length); } + @Override public void write(byte[] b, int off, int len) throws IOException { - if (len == 0) return; - + if (len == 0) { + return; + } + if (len < 0 || b.length < off+len) { throw new IOException("not enough bytes in your input buffer"); } - + + final int chunkMask = getChunkMask(); while (len > 0) { int posInChunk = (int)(_pos & chunkMask); - int nextLen = Math.min(chunkSize-posInChunk, len); + int nextLen = Math.min(_chunk.length-posInChunk, len); System.arraycopy(b, off, _chunk, posInChunk, nextLen); _pos += nextLen; off += nextLen; len -= nextLen; if ((_pos & chunkMask) == 0) { - try { - writeChunk(); - } catch (GeneralSecurityException e) { - throw new IOException(e); - } + writeChunk(len > 0); } } } - protected void writeChunk() throws IOException, GeneralSecurityException { - int posInChunk = (int)(_pos & chunkMask); + private int getChunkMask() { + 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) // but if called on close(), posInChunk is somewhere within the chunk data - int index = (int)(_pos >> chunkBits); + int index = (int)(_pos >> _chunkBits); boolean lastChunk; if (posInChunk==0) { index--; - posInChunk = chunkSize; + posInChunk = _chunk.length; lastChunk = false; } else { // pad the last chunk lastChunk = true; } - _cipher = initCipherForBlock(_cipher, index, lastChunk); + int ciLen; + try { + 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); } - + + @Override public void close() throws IOException { try { - writeChunk(); + writeChunk(false); super.close(); - - int oleStreamSize = (int)(fileOut.length()+LittleEndianConsts.LONG_SIZE); - calculateChecksum(fileOut, (int)_pos); - dir.createDocument(DEFAULT_POIFS_ENTRY, oleStreamSize, new EncryptedPackageWriter()); - createEncryptionInfoEntry(dir, fileOut); + + if (_fileOut != null) { + int oleStreamSize = (int)(_fileOut.length()+LittleEndianConsts.LONG_SIZE); + calculateChecksum(_fileOut, (int)_pos); + _dir.createDocument(DEFAULT_POIFS_ENTRY, oleStreamSize, new EncryptedPackageWriter()); + createEncryptionInfoEntry(_dir, _fileOut); + } } catch (GeneralSecurityException e) { throw new IOException(e); } } private class EncryptedPackageWriter implements POIFSWriterListener { + @Override public void processPOIFSWriterEvent(POIFSWriterEvent event) { try { OutputStream os = event.getStream(); - byte buf[] = new byte[chunkSize]; - - // StreamSize (8 bytes): An unsigned integer that specifies the number of bytes used by data - // encrypted within the EncryptedData field, not including the size of the StreamSize field. - // Note that the actual size of the \EncryptedPackage stream (1) can be larger than this - // value, depending on the block size of the chosen encryption algorithm - LittleEndian.putLong(buf, 0, _pos); - os.write(buf, 0, LittleEndian.LONG_SIZE); - FileInputStream fis = new FileInputStream(fileOut); - int readBytes; - while ((readBytes = fis.read(buf)) != -1) { - os.write(buf, 0, readBytes); - } + // StreamSize (8 bytes): An unsigned integer that specifies the number of bytes used by data + // encrypted within the EncryptedData field, not including the size of the StreamSize field. + // Note that the actual size of the \EncryptedPackage stream (1) can be larger than this + // value, depending on the block size of the chosen encryption algorithm + byte buf[] = new byte[LittleEndianConsts.LONG_SIZE]; + LittleEndian.putLong(buf, 0, _pos); + os.write(buf); + + FileInputStream fis = new FileInputStream(_fileOut); + IOUtils.copy(fis, os); fis.close(); os.close(); - - if (!fileOut.delete()) { - logger.log(POILogger.ERROR, "Can't delete temporary encryption file: "+fileOut); + + if (!_fileOut.delete()) { + LOG.log(POILogger.ERROR, "Can't delete temporary encryption file: "+_fileOut); } } catch (IOException e) { throw new EncryptedDocumentException(e); diff --git a/src/java/org/apache/poi/poifs/crypt/Decryptor.java b/src/java/org/apache/poi/poifs/crypt/Decryptor.java index bec436a88e..41621853ea 100644 --- a/src/java/org/apache/poi/poifs/crypt/Decryptor.java +++ b/src/java/org/apache/poi/poifs/crypt/Decryptor.java @@ -20,24 +20,26 @@ import java.io.IOException; import java.io.InputStream; import java.security.GeneralSecurityException; +import javax.crypto.Cipher; import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; import org.apache.poi.EncryptedDocumentException; import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.NPOIFSFileSystem; import org.apache.poi.poifs.filesystem.OPOIFSFileSystem; 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_POIFS_ENTRY="EncryptedPackage"; - protected final EncryptionInfoBuilder builder; + protected EncryptionInfo encryptionInfo; private SecretKey secretKey; private byte[] verifier, integrityHmacKey, integrityHmacValue; - protected Decryptor(EncryptionInfoBuilder builder) { - this.builder = builder; + protected Decryptor() { } /** @@ -54,6 +56,45 @@ public abstract class Decryptor { public abstract InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException; + /** + * Wraps a stream for decryption
+ *
+ * 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)
throws GeneralSecurityException;
@@ -85,9 +126,11 @@ public abstract class Decryptor {
public InputStream getDataStream(NPOIFSFileSystem fs) throws IOException, GeneralSecurityException {
return getDataStream(fs.getRoot());
}
+
public InputStream getDataStream(OPOIFSFileSystem fs) throws IOException, GeneralSecurityException {
return getDataStream(fs.getRoot());
}
+
public InputStream getDataStream(POIFSFileSystem fs) throws IOException, GeneralSecurityException {
return getDataStream(fs.getRoot());
}
@@ -126,10 +169,29 @@ public abstract class Decryptor {
}
protected int getBlockSizeInBytes() {
- return builder.getHeader().getBlockSize();
+ return encryptionInfo.getHeader().getBlockSize();
}
protected int getKeySizeInBytes() {
- return builder.getHeader().getKeySize()/8;
+ return encryptionInfo.getHeader().getKeySize()/8;
+ }
+
+ public EncryptionInfo getEncryptionInfo() {
+ return encryptionInfo;
+ }
+
+ public void setEncryptionInfo(EncryptionInfo encryptionInfo) {
+ this.encryptionInfo = encryptionInfo;
+ }
+
+ @Override
+ public Decryptor clone() throws CloneNotSupportedException {
+ Decryptor other = (Decryptor)super.clone();
+ other.integrityHmacKey = integrityHmacKey.clone();
+ other.integrityHmacValue = integrityHmacValue.clone();
+ other.verifier = verifier.clone();
+ other.secretKey = new SecretKeySpec(secretKey.getEncoded(), secretKey.getAlgorithm());
+ // encryptionInfo is set from outside
+ return other;
}
}
\ No newline at end of file
diff --git a/src/java/org/apache/poi/poifs/crypt/EncryptionHeader.java b/src/java/org/apache/poi/poifs/crypt/EncryptionHeader.java
index b6a0e2d0c4..c52e87c62b 100644
--- a/src/java/org/apache/poi/poifs/crypt/EncryptionHeader.java
+++ b/src/java/org/apache/poi/poifs/crypt/EncryptionHeader.java
@@ -16,12 +16,11 @@
==================================================================== */
package org.apache.poi.poifs.crypt;
-
/**
* Reads and processes OOXML Encryption Headers
* The constants are largely based on ZIP constants.
*/
-public abstract class EncryptionHeader {
+public abstract class EncryptionHeader implements Cloneable {
public static final int ALGORITHM_RC4 = CipherAlgorithm.rc4.ecmaId;
public static final int ALGORITHM_AES_128 = CipherAlgorithm.aes128.ecmaId;
public static final int ALGORITHM_AES_192 = CipherAlgorithm.aes192.ecmaId;
@@ -132,4 +131,11 @@ public abstract class EncryptionHeader {
protected void setCspName(String cspName) {
this.cspName = cspName;
}
+
+ @Override
+ public EncryptionHeader clone() throws CloneNotSupportedException {
+ EncryptionHeader other = (EncryptionHeader)super.clone();
+ other.keySalt = (keySalt == null) ? null : keySalt.clone();
+ return other;
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java b/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java
index 780409e191..34b83cb8c3 100644
--- a/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java
+++ b/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java
@@ -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 versionMinor;
private final int encryptionFlags;
- private final EncryptionHeader header;
- private final EncryptionVerifier verifier;
- private final Decryptor decryptor;
- private final Encryptor encryptor;
+ private EncryptionHeader header;
+ private EncryptionVerifier verifier;
+ private Decryptor decryptor;
+ private Encryptor encryptor;
/**
* A flag that specifies whether CryptoAPI RC4 or ECMA-376 encryption
@@ -96,11 +96,10 @@ public class EncryptionInfo {
public EncryptionInfo(LittleEndianInput dis, boolean isCryptoAPI) throws IOException {
final EncryptionMode encryptionMode;
- versionMajor = dis.readShort();
- versionMinor = dis.readShort();
+ versionMajor = dis.readUShort();
+ versionMinor = dis.readUShort();
- if (!isCryptoAPI
- && versionMajor == binaryRC4.versionMajor
+ if ( versionMajor == binaryRC4.versionMajor
&& versionMinor == binaryRC4.versionMinor) {
encryptionMode = binaryRC4;
encryptionFlags = -1;
@@ -138,10 +137,6 @@ public class EncryptionInfo {
}
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);
-
- header = eib.getHeader();
- verifier = eib.getVerifier();
- decryptor = eib.getDecryptor();
- encryptor = eib.getEncryptor();
}
protected static EncryptionInfoBuilder getBuilder(EncryptionMode encryptionMode)
@@ -229,4 +219,32 @@ public class EncryptionInfo {
public Encryptor getEncryptor() {
return encryptor;
}
-}
+
+ public void setHeader(EncryptionHeader header) {
+ this.header = header;
+ }
+
+ public void setVerifier(EncryptionVerifier verifier) {
+ this.verifier = verifier;
+ }
+
+ public void setDecryptor(Decryptor decryptor) {
+ this.decryptor = decryptor;
+ }
+
+ public void setEncryptor(Encryptor encryptor) {
+ this.encryptor = encryptor;
+ }
+
+ @Override
+ public EncryptionInfo clone() throws CloneNotSupportedException {
+ EncryptionInfo other = (EncryptionInfo)super.clone();
+ other.header = header.clone();
+ other.verifier = verifier.clone();
+ other.decryptor = decryptor.clone();
+ other.decryptor.setEncryptionInfo(other);
+ other.encryptor = encryptor.clone();
+ other.encryptor.setEncryptionInfo(other);
+ return other;
+ }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/poi/poifs/crypt/EncryptionInfoBuilder.java b/src/java/org/apache/poi/poifs/crypt/EncryptionInfoBuilder.java
index e36d44da9e..24371dfdd6 100644
--- a/src/java/org/apache/poi/poifs/crypt/EncryptionInfoBuilder.java
+++ b/src/java/org/apache/poi/poifs/crypt/EncryptionInfoBuilder.java
@@ -30,24 +30,4 @@ public interface EncryptionInfoBuilder {
* initialize the builder from scratch
*/
void initialize(EncryptionInfo ei, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode);
-
- /**
- * @return the header data
- */
- EncryptionHeader getHeader();
-
- /**
- * @return the verifier data
- */
- EncryptionVerifier getVerifier();
-
- /**
- * @return the decryptor
- */
- Decryptor getDecryptor();
-
- /**
- * @return the encryptor
- */
- Encryptor getEncryptor();
}
diff --git a/src/java/org/apache/poi/poifs/crypt/EncryptionVerifier.java b/src/java/org/apache/poi/poifs/crypt/EncryptionVerifier.java
index 2688b50f41..6e8059261a 100644
--- a/src/java/org/apache/poi/poifs/crypt/EncryptionVerifier.java
+++ b/src/java/org/apache/poi/poifs/crypt/EncryptionVerifier.java
@@ -16,11 +16,10 @@
==================================================================== */
package org.apache.poi.poifs.crypt;
-
/**
* Used when checking if a key is valid for a document
*/
-public abstract class EncryptionVerifier {
+public abstract class EncryptionVerifier implements Cloneable {
private byte[] salt;
private byte[] encryptedVerifier;
private byte[] encryptedVerifierHash;
@@ -105,5 +104,13 @@ public abstract class EncryptionVerifier {
this.hashAlgorithm = hashAlgorithm;
}
-
+ @Override
+ public EncryptionVerifier clone() throws CloneNotSupportedException {
+ EncryptionVerifier other = (EncryptionVerifier)super.clone();
+ other.salt = (salt == null) ? null : salt.clone();
+ other.encryptedVerifier = (encryptedVerifier == null) ? null : encryptedVerifier.clone();
+ other.encryptedVerifierHash = (encryptedVerifierHash == null) ? null : encryptedVerifierHash.clone();
+ other.encryptedKey = (encryptedKey == null) ? null : encryptedKey.clone();
+ return other;
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/Encryptor.java b/src/java/org/apache/poi/poifs/crypt/Encryptor.java
index d40f8ae5d9..d92fc30977 100644
--- a/src/java/org/apache/poi/poifs/crypt/Encryptor.java
+++ b/src/java/org/apache/poi/poifs/crypt/Encryptor.java
@@ -21,14 +21,16 @@ import java.io.OutputStream;
import java.security.GeneralSecurityException;
import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
import org.apache.poi.poifs.filesystem.OPOIFSFileSystem;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
-public abstract class Encryptor {
+public abstract class Encryptor implements Cloneable {
protected static final String DEFAULT_POIFS_ENTRY = Decryptor.DEFAULT_POIFS_ENTRY;
+ private EncryptionInfo encryptionInfo;
private SecretKey secretKey;
/**
@@ -66,4 +68,20 @@ public abstract class Encryptor {
protected void setSecretKey(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;
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Decryptor.java b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Decryptor.java
index 5fb39d9216..1fdf3a9820 100644
--- a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Decryptor.java
+++ b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Decryptor.java
@@ -29,36 +29,45 @@ import javax.crypto.spec.SecretKeySpec;
import org.apache.poi.EncryptedDocumentException;
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.DocumentInputStream;
import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianInput;
import org.apache.poi.util.StringUtil;
-public class BinaryRC4Decryptor extends Decryptor {
+public class BinaryRC4Decryptor extends Decryptor implements Cloneable {
private long _length = -1L;
+ private int _chunkSize = 512;
private class BinaryRC4CipherInputStream extends ChunkedCipherInputStream {
+ @Override
protected Cipher initCipherForBlock(Cipher existing, int block)
throws GeneralSecurityException {
- return BinaryRC4Decryptor.initCipherForBlock(existing, block, builder, getSecretKey(), Cipher.DECRYPT_MODE);
+ return BinaryRC4Decryptor.this.initCipherForBlock(existing, block);
}
public BinaryRC4CipherInputStream(DocumentInputStream stream, long size)
throws GeneralSecurityException {
- super(stream, size, 512);
+ super(stream, size, _chunkSize);
}
+
+ public BinaryRC4CipherInputStream(LittleEndianInput stream)
+ throws GeneralSecurityException {
+ super(stream, Integer.MAX_VALUE, _chunkSize);
+ }
}
- protected BinaryRC4Decryptor(BinaryRC4EncryptionInfoBuilder builder) {
- super(builder);
+ protected BinaryRC4Decryptor() {
}
+ @Override
public boolean verifyPassword(String password) {
- EncryptionVerifier ver = builder.getVerifier();
+ EncryptionVerifier ver = getEncryptionInfo().getVerifier();
SecretKey skey = generateSecretKey(password, ver);
try {
- Cipher cipher = initCipherForBlock(null, 0, builder, skey, Cipher.DECRYPT_MODE);
+ Cipher cipher = initCipherForBlock(null, 0, getEncryptionInfo(), skey, Cipher.DECRYPT_MODE);
byte encryptedVerifier[] = ver.getEncryptedVerifier();
byte verifier[] = new byte[encryptedVerifier.length];
cipher.update(encryptedVerifier, 0, encryptedVerifier.length, verifier);
@@ -78,17 +87,23 @@ public class BinaryRC4Decryptor extends Decryptor {
return false;
}
- protected static Cipher initCipherForBlock(Cipher cipher, int block,
- EncryptionInfoBuilder builder, SecretKey skey, int encryptMode)
+ @Override
+ public Cipher initCipherForBlock(Cipher cipher, int block)
throws GeneralSecurityException {
- EncryptionVerifier ver = builder.getVerifier();
+ return initCipherForBlock(cipher, block, getEncryptionInfo(), getSecretKey(), Cipher.DECRYPT_MODE);
+ }
+
+ protected static Cipher initCipherForBlock(Cipher cipher, int block,
+ EncryptionInfo encryptionInfo, SecretKey skey, int encryptMode)
+ throws GeneralSecurityException {
+ EncryptionVerifier ver = encryptionInfo.getVerifier();
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
byte blockKey[] = new byte[4];
LittleEndian.putUInt(blockKey, 0, block);
byte encKey[] = CryptoFunctions.generateKey(skey.getEncoded(), hashAlgo, blockKey, 16);
SecretKey key = new SecretKeySpec(encKey, skey.getAlgorithm());
if (cipher == null) {
- EncryptionHeader em = builder.getHeader();
+ EncryptionHeader em = encryptionInfo.getHeader();
cipher = CryptoFunctions.getCipher(key, em.getCipherAlgorithm(), null, null, encryptMode);
} else {
cipher.init(encryptMode, key);
@@ -96,10 +111,10 @@ public class BinaryRC4Decryptor extends Decryptor {
return cipher;
}
- protected static SecretKey generateSecretKey(String password,
- EncryptionVerifier ver) {
- if (password.length() > 255)
+ protected static SecretKey generateSecretKey(String password, EncryptionVerifier ver) {
+ if (password.length() > 255) {
password = password.substring(0, 255);
+ }
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
MessageDigest hashAlg = CryptoFunctions.getMessageDigest(hashAlgo);
byte hash[] = hashAlg.digest(StringUtil.getToUnicodeLE(password));
@@ -116,15 +131,22 @@ public class BinaryRC4Decryptor extends Decryptor {
return skey;
}
+ @Override
@SuppressWarnings("resource")
- public InputStream getDataStream(DirectoryNode dir) throws IOException,
+ public ChunkedCipherInputStream getDataStream(DirectoryNode dir) throws IOException,
GeneralSecurityException {
DocumentInputStream dis = dir.createDocumentInputStream(DEFAULT_POIFS_ENTRY);
_length = dis.readLong();
- BinaryRC4CipherInputStream cipherStream = new BinaryRC4CipherInputStream(dis, _length);
- return cipherStream;
+ return new BinaryRC4CipherInputStream(dis, _length);
}
+
+ public InputStream getDataStream(LittleEndianInput stream)
+ throws IOException, GeneralSecurityException {
+ return new BinaryRC4CipherInputStream(stream);
+ }
+
+ @Override
public long getLength() {
if (_length == -1L) {
throw new IllegalStateException("Decryptor.getDataStream() was not called");
@@ -132,4 +154,14 @@ public class BinaryRC4Decryptor extends Decryptor {
return _length;
}
+
+ @Override
+ public void setChunkSize(int chunkSize) {
+ _chunkSize = chunkSize;
+ }
+
+ @Override
+ public BinaryRC4Decryptor clone() throws CloneNotSupportedException {
+ return (BinaryRC4Decryptor)super.clone();
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionHeader.java b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionHeader.java
index 1b811a1031..b9022017b9 100644
--- a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionHeader.java
+++ b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionHeader.java
@@ -24,8 +24,7 @@ import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
-public class BinaryRC4EncryptionHeader extends EncryptionHeader implements
- EncryptionRecord {
+public class BinaryRC4EncryptionHeader extends EncryptionHeader implements EncryptionRecord, Cloneable {
protected BinaryRC4EncryptionHeader() {
setCipherAlgorithm(CipherAlgorithm.rc4);
@@ -39,6 +38,14 @@ public class BinaryRC4EncryptionHeader extends EncryptionHeader implements
setChainingMode(null);
}
+ @Override
public void write(LittleEndianByteArrayOutputStream littleendianbytearrayoutputstream) {
}
+
+ @Override
+ public BinaryRC4EncryptionHeader clone() throws CloneNotSupportedException {
+ return (BinaryRC4EncryptionHeader)super.clone();
+ }
+
+
}
diff --git a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionInfoBuilder.java b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionInfoBuilder.java
index 10bf58d83b..94ddde72ab 100644
--- a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionInfoBuilder.java
+++ b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionInfoBuilder.java
@@ -23,55 +23,37 @@ import org.apache.poi.util.LittleEndianInput;
public class BinaryRC4EncryptionInfoBuilder implements EncryptionInfoBuilder {
- EncryptionInfo info;
- BinaryRC4EncryptionHeader header;
- BinaryRC4EncryptionVerifier verifier;
- BinaryRC4Decryptor decryptor;
- BinaryRC4Encryptor encryptor;
-
public BinaryRC4EncryptionInfoBuilder() {
}
+ @Override
public void initialize(EncryptionInfo info, LittleEndianInput dis)
throws IOException {
- this.info = info;
int vMajor = info.getVersionMajor();
int vMinor = info.getVersionMinor();
assert (vMajor == 1 && vMinor == 1);
- header = new BinaryRC4EncryptionHeader();
- verifier = new BinaryRC4EncryptionVerifier(dis);
- decryptor = new BinaryRC4Decryptor(this);
- encryptor = new BinaryRC4Encryptor(this);
+ info.setHeader(new BinaryRC4EncryptionHeader());
+ info.setVerifier(new BinaryRC4EncryptionVerifier(dis));
+ Decryptor dec = new BinaryRC4Decryptor();
+ dec.setEncryptionInfo(info);
+ info.setDecryptor(dec);
+ Encryptor enc = new BinaryRC4Encryptor();
+ enc.setEncryptionInfo(info);
+ info.setEncryptor(enc);
}
+ @Override
public void initialize(EncryptionInfo info,
CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm,
int keyBits, int blockSize, ChainingMode chainingMode) {
- this.info = info;
- header = new BinaryRC4EncryptionHeader();
- verifier = new BinaryRC4EncryptionVerifier();
- decryptor = new BinaryRC4Decryptor(this);
- encryptor = new BinaryRC4Encryptor(this);
- }
-
- public BinaryRC4EncryptionHeader getHeader() {
- return header;
- }
-
- public BinaryRC4EncryptionVerifier getVerifier() {
- return verifier;
- }
-
- public BinaryRC4Decryptor getDecryptor() {
- return decryptor;
- }
-
- public BinaryRC4Encryptor getEncryptor() {
- return encryptor;
- }
-
- public EncryptionInfo getEncryptionInfo() {
- return info;
+ info.setHeader(new BinaryRC4EncryptionHeader());
+ info.setVerifier(new BinaryRC4EncryptionVerifier());
+ Decryptor dec = new BinaryRC4Decryptor();
+ dec.setEncryptionInfo(info);
+ info.setDecryptor(dec);
+ Encryptor enc = new BinaryRC4Encryptor();
+ enc.setEncryptionInfo(info);
+ info.setEncryptor(enc);
}
}
diff --git a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionVerifier.java b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionVerifier.java
index 86cf4ac184..654b64a3e5 100644
--- a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionVerifier.java
+++ b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionVerifier.java
@@ -23,7 +23,7 @@ import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
import org.apache.poi.util.LittleEndianInput;
-public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements EncryptionRecord {
+public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements EncryptionRecord, Cloneable {
protected BinaryRC4EncryptionVerifier() {
setSpinCount(-1);
@@ -50,6 +50,7 @@ public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements E
setHashAlgorithm(HashAlgorithm.md5);
}
+ @Override
protected void setSalt(byte salt[]) {
if (salt == null || salt.length != 16) {
throw new EncryptedDocumentException("invalid verifier salt");
@@ -58,14 +59,17 @@ public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements E
super.setSalt(salt);
}
+ @Override
protected void setEncryptedVerifier(byte encryptedVerifier[]) {
super.setEncryptedVerifier(encryptedVerifier);
}
+ @Override
protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) {
super.setEncryptedVerifierHash(encryptedVerifierHash);
}
+ @Override
public void write(LittleEndianByteArrayOutputStream bos) {
byte salt[] = getSalt();
assert (salt.length == 16);
@@ -78,4 +82,8 @@ public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements E
bos.write(encryptedVerifierHash);
}
+ @Override
+ public BinaryRC4EncryptionVerifier clone() throws CloneNotSupportedException {
+ return (BinaryRC4EncryptionVerifier)super.clone();
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Encryptor.java b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Encryptor.java
index 2cf2d93347..ef49c9dc78 100644
--- a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Encryptor.java
+++ b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Encryptor.java
@@ -34,24 +34,95 @@ import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.poifs.crypt.DataSpaceMapUtils;
import org.apache.poi.poifs.crypt.EncryptionInfo;
import org.apache.poi.poifs.crypt.Encryptor;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
-public class BinaryRC4Encryptor extends Encryptor {
+public class BinaryRC4Encryptor extends Encryptor implements Cloneable {
+
+ 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();
+ }
- private final BinaryRC4EncryptionInfoBuilder builder;
-
protected class BinaryRC4CipherOutputStream extends ChunkedCipherOutputStream {
+ @Override
protected Cipher initCipherForBlock(Cipher cipher, int block, boolean lastChunk)
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) {
}
+ @Override
protected void createEncryptionInfoEntry(DirectoryNode dir, File tmpFile)
throws IOException, GeneralSecurityException {
BinaryRC4Encryptor.this.createEncryptionInfoEntry(dir);
@@ -62,66 +133,4 @@ public class BinaryRC4Encryptor extends Encryptor {
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);
- }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDecryptor.java b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDecryptor.java
index 09f82e5a6a..07b7910749 100644
--- a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDecryptor.java
+++ b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDecryptor.java
@@ -17,7 +17,6 @@
package org.apache.poi.poifs.crypt.cryptoapi;
-import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -27,79 +26,34 @@ import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
-import javax.crypto.ShortBufferException;
import javax.crypto.spec.SecretKeySpec;
import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.poifs.crypt.ChunkedCipherInputStream;
import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.poifs.crypt.Decryptor;
import org.apache.poi.poifs.crypt.EncryptionHeader;
-import org.apache.poi.poifs.crypt.EncryptionInfoBuilder;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
import org.apache.poi.poifs.crypt.EncryptionVerifier;
import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.DocumentInputStream;
import org.apache.poi.poifs.filesystem.DocumentNode;
-import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.BoundedInputStream;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianInput;
import org.apache.poi.util.LittleEndianInputStream;
import org.apache.poi.util.StringUtil;
-public class CryptoAPIDecryptor extends Decryptor {
+public class CryptoAPIDecryptor extends Decryptor implements Cloneable {
private long _length;
+ private int _chunkSize = -1;
- private class SeekableByteArrayInputStream extends ByteArrayInputStream {
- Cipher cipher;
- byte oneByte[] = { 0 };
-
- public void seek(int newpos) {
- if (newpos > count) {
- throw new ArrayIndexOutOfBoundsException(newpos);
- }
-
- this.pos = newpos;
- mark = newpos;
- }
-
- public void setBlock(int block) throws GeneralSecurityException {
- cipher = initCipherForBlock(cipher, block);
- }
-
- public synchronized int read() {
- int ch = super.read();
- if (ch == -1) return -1;
- oneByte[0] = (byte) ch;
- try {
- cipher.update(oneByte, 0, 1, oneByte);
- } catch (ShortBufferException e) {
- throw new EncryptedDocumentException(e);
- }
- return oneByte[0];
- }
-
- public synchronized int read(byte b[], int off, int len) {
- int readLen = super.read(b, off, len);
- if (readLen ==-1) return -1;
- try {
- cipher.update(b, off, readLen, b, off);
- } catch (ShortBufferException e) {
- throw new EncryptedDocumentException(e);
- }
- return readLen;
- }
-
- public SeekableByteArrayInputStream(byte buf[])
- throws GeneralSecurityException {
- super(buf);
- cipher = initCipherForBlock(null, 0);
- }
- }
-
static class StreamDescriptorEntry {
static BitField flagStream = BitFieldFactory.getInstance(1);
@@ -111,16 +65,16 @@ public class CryptoAPIDecryptor extends Decryptor {
String streamName;
}
- protected CryptoAPIDecryptor(CryptoAPIEncryptionInfoBuilder builder) {
- super(builder);
+ protected CryptoAPIDecryptor() {
_length = -1L;
}
+ @Override
public boolean verifyPassword(String password) {
- EncryptionVerifier ver = builder.getVerifier();
+ EncryptionVerifier ver = getEncryptionInfo().getVerifier();
SecretKey skey = generateSecretKey(password, ver);
try {
- Cipher cipher = initCipherForBlock(null, 0, builder, skey, Cipher.DECRYPT_MODE);
+ Cipher cipher = initCipherForBlock(null, 0, getEncryptionInfo(), skey, Cipher.DECRYPT_MODE);
byte encryptedVerifier[] = ver.getEncryptedVerifier();
byte verifier[] = new byte[encryptedVerifier.length];
cipher.update(encryptedVerifier, 0, encryptedVerifier.length, verifier);
@@ -140,30 +94,25 @@ public class CryptoAPIDecryptor extends Decryptor {
return false;
}
- /**
- * Initializes a cipher object for a given block index for decryption
- *
- * @param cipher may be null, otherwise the given instance is reset to the new block index
- * @param block the block index, e.g. the persist/slide id (hslf)
- * @return a new cipher object, if cipher was null, otherwise the reinitialized cipher
- * @throws GeneralSecurityException
- */
+ @Override
public Cipher initCipherForBlock(Cipher cipher, int block)
throws GeneralSecurityException {
- return initCipherForBlock(cipher, block, builder, getSecretKey(), Cipher.DECRYPT_MODE);
+ EncryptionInfo ei = getEncryptionInfo();
+ SecretKey sk = getSecretKey();
+ return initCipherForBlock(cipher, block, ei, sk, Cipher.DECRYPT_MODE);
}
protected static Cipher initCipherForBlock(Cipher cipher, int block,
- EncryptionInfoBuilder builder, SecretKey skey, int encryptMode)
+ EncryptionInfo encryptionInfo, SecretKey skey, int encryptMode)
throws GeneralSecurityException {
- EncryptionVerifier ver = builder.getVerifier();
+ EncryptionVerifier ver = encryptionInfo.getVerifier();
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
byte blockKey[] = new byte[4];
LittleEndian.putUInt(blockKey, 0, block);
MessageDigest hashAlg = CryptoFunctions.getMessageDigest(hashAlgo);
hashAlg.update(skey.getEncoded());
byte encKey[] = hashAlg.digest(blockKey);
- EncryptionHeader header = builder.getHeader();
+ EncryptionHeader header = encryptionInfo.getHeader();
int keyBits = header.getKeySize();
encKey = CryptoFunctions.getBlock0(encKey, keyBits / 8);
if (keyBits == 40) {
@@ -190,6 +139,18 @@ public class CryptoAPIDecryptor extends Decryptor {
return skey;
}
+ @Override
+ public ChunkedCipherInputStream getDataStream(DirectoryNode dir)
+ throws IOException, GeneralSecurityException {
+ throw new IOException("not supported");
+ }
+
+ @Override
+ public ChunkedCipherInputStream getDataStream(LittleEndianInput stream, int size, int initialPos)
+ throws IOException, GeneralSecurityException {
+ return new CryptoAPICipherInputStream(stream, size, initialPos);
+ }
+
/**
* Decrypt the Document-/SummaryInformation and other optionally streams.
* Opposed to other crypto modes, cryptoapi is record based and can't be used
@@ -197,15 +158,17 @@ public class CryptoAPIDecryptor extends Decryptor {
*
* @see 2.3.5.4 RC4 CryptoAPI Encrypted Summary Stream
*/
- public InputStream getDataStream(DirectoryNode dir)
+ public POIFSFileSystem getSummaryEntries(DirectoryNode root, String encryptedStream)
throws IOException, GeneralSecurityException {
- NPOIFSFileSystem fsOut = new NPOIFSFileSystem();
- DocumentNode es = (DocumentNode) dir.getEntry("EncryptedSummary");
- DocumentInputStream dis = dir.createDocumentInputStream(es);
+ POIFSFileSystem fsOut = new POIFSFileSystem();
+ // HSLF: encryptedStream
+ // HSSF: encryption
+ DocumentNode es = (DocumentNode) root.getEntry(encryptedStream);
+ DocumentInputStream dis = root.createDocumentInputStream(es);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
IOUtils.copy(dis, bos);
dis.close();
- SeekableByteArrayInputStream sbis = new SeekableByteArrayInputStream(bos.toByteArray());
+ CryptoAPIDocumentInputStream sbis = new CryptoAPIDocumentInputStream(this, bos.toByteArray());
LittleEndianInputStream leis = new LittleEndianInputStream(sbis);
int streamDescriptorArrayOffset = (int) leis.readUInt();
/* int streamDescriptorArraySize = (int) */ leis.readUInt();
@@ -239,21 +202,40 @@ public class CryptoAPIDecryptor extends Decryptor {
leis.close();
sbis.close();
sbis = null;
- bos.reset();
- fsOut.writeFilesystem(bos);
- fsOut.close();
- _length = bos.size();
- ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
- return bis;
+ return fsOut;
}
/**
* @return the length of the stream returned by {@link #getDataStream(DirectoryNode)}
*/
+ @Override
public long getLength() {
if (_length == -1L) {
throw new IllegalStateException("Decryptor.getDataStream() was not called");
}
return _length;
}
+
+ public void setChunkSize(int chunkSize) {
+ _chunkSize = chunkSize;
+ }
+
+ @Override
+ public CryptoAPIDecryptor clone() throws CloneNotSupportedException {
+ return (CryptoAPIDecryptor)super.clone();
+ }
+
+ private class CryptoAPICipherInputStream extends ChunkedCipherInputStream {
+
+ @Override
+ protected Cipher initCipherForBlock(Cipher existing, int block)
+ throws GeneralSecurityException {
+ return CryptoAPIDecryptor.this.initCipherForBlock(existing, block);
+ }
+
+ public CryptoAPICipherInputStream(LittleEndianInput stream, long size, int initialPos)
+ throws GeneralSecurityException {
+ super(stream, size, _chunkSize, initialPos);
+ }
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentInputStream.java b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentInputStream.java
new file mode 100644
index 0000000000..5736649321
--- /dev/null
+++ b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentInputStream.java
@@ -0,0 +1,86 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.poifs.crypt.cryptoapi;
+
+import java.io.ByteArrayInputStream;
+import java.security.GeneralSecurityException;
+
+import javax.crypto.Cipher;
+import javax.crypto.ShortBufferException;
+
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.util.Internal;
+
+/**
+ * A seekable InputStream, which is used to decrypt/extract the document entries
+ * within the encrypted stream
+ */
+@Internal
+/* package */ class CryptoAPIDocumentInputStream extends ByteArrayInputStream {
+ private Cipher cipher;
+ private final CryptoAPIDecryptor decryptor;
+ private byte oneByte[] = { 0 };
+
+ public void seek(int newpos) {
+ if (newpos > count) {
+ throw new ArrayIndexOutOfBoundsException(newpos);
+ }
+
+ this.pos = newpos;
+ mark = newpos;
+ }
+
+ public void setBlock(int block) throws GeneralSecurityException {
+ cipher = decryptor.initCipherForBlock(cipher, block);
+ }
+
+ @Override
+ public synchronized int read() {
+ int ch = super.read();
+ if (ch == -1) {
+ return -1;
+ }
+ oneByte[0] = (byte) ch;
+ try {
+ cipher.update(oneByte, 0, 1, oneByte);
+ } catch (ShortBufferException e) {
+ throw new EncryptedDocumentException(e);
+ }
+ return oneByte[0];
+ }
+
+ @Override
+ public synchronized int read(byte b[], int off, int len) {
+ int readLen = super.read(b, off, len);
+ if (readLen ==-1) {
+ return -1;
+ }
+ try {
+ cipher.update(b, off, readLen, b, off);
+ } catch (ShortBufferException e) {
+ throw new EncryptedDocumentException(e);
+ }
+ return readLen;
+ }
+
+ public CryptoAPIDocumentInputStream(CryptoAPIDecryptor decryptor, byte buf[])
+ throws GeneralSecurityException {
+ super(buf);
+ this.decryptor = decryptor;
+ cipher = decryptor.initCipherForBlock(null, 0);
+ }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentOutputStream.java b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentOutputStream.java
new file mode 100644
index 0000000000..6bf04871dc
--- /dev/null
+++ b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentOutputStream.java
@@ -0,0 +1,74 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.poifs.crypt.cryptoapi;
+
+import java.io.ByteArrayOutputStream;
+import java.security.GeneralSecurityException;
+
+import javax.crypto.Cipher;
+
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.util.Internal;
+
+/**
+ * An OutputStream for the document entries within the encrypted stream
+ */
+@Internal
+/* package */ class CryptoAPIDocumentOutputStream extends ByteArrayOutputStream {
+ private Cipher cipher;
+ private CryptoAPIEncryptor encryptor;
+ private byte oneByte[] = { 0 };
+
+ public CryptoAPIDocumentOutputStream(CryptoAPIEncryptor encryptor) throws GeneralSecurityException {
+ this.encryptor = encryptor;
+ setBlock(0);
+ }
+
+ public byte[] getBuf() {
+ return buf;
+ }
+
+ public void setSize(int count) {
+ this.count = count;
+ }
+
+ public void setBlock(int block) throws GeneralSecurityException {
+ cipher = encryptor.initCipherForBlock(cipher, block);
+ }
+
+ @Override
+ public void write(int b) {
+ try {
+ oneByte[0] = (byte)b;
+ cipher.update(oneByte, 0, 1, oneByte, 0);
+ super.write(oneByte);
+ } catch (Exception e) {
+ throw new EncryptedDocumentException(e);
+ }
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) {
+ try {
+ cipher.update(b, off, len, b, off);
+ super.write(b, off, len);
+ } catch (Exception e) {
+ throw new EncryptedDocumentException(e);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionHeader.java b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionHeader.java
index 151b6588ae..b54dc2f0d5 100644
--- a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionHeader.java
+++ b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionHeader.java
@@ -27,7 +27,7 @@ import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.crypt.standard.StandardEncryptionHeader;
import org.apache.poi.util.LittleEndianInput;
-public class CryptoAPIEncryptionHeader extends StandardEncryptionHeader {
+public class CryptoAPIEncryptionHeader extends StandardEncryptionHeader implements Cloneable {
public CryptoAPIEncryptionHeader(LittleEndianInput is) throws IOException {
super(is);
@@ -39,6 +39,7 @@ public class CryptoAPIEncryptionHeader extends StandardEncryptionHeader {
super(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
}
+ @Override
public void setKeySize(int keyBits) {
// Microsoft Base Cryptographic Provider is limited up to 40 bits
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa375599(v=vs.85).aspx
@@ -59,4 +60,9 @@ public class CryptoAPIEncryptionHeader extends StandardEncryptionHeader {
setCspName(CipherProvider.rc4.cipherProviderName);
}
}
+
+ @Override
+ public CryptoAPIEncryptionHeader clone() throws CloneNotSupportedException {
+ return (CryptoAPIEncryptionHeader)super.clone();
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionInfoBuilder.java b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionInfoBuilder.java
index 36df528766..fef4dde168 100644
--- a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionInfoBuilder.java
+++ b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionInfoBuilder.java
@@ -23,63 +23,52 @@ import org.apache.poi.poifs.crypt.*;
import org.apache.poi.util.LittleEndianInput;
public class CryptoAPIEncryptionInfoBuilder implements EncryptionInfoBuilder {
- EncryptionInfo info;
- CryptoAPIEncryptionHeader header;
- CryptoAPIEncryptionVerifier verifier;
- CryptoAPIDecryptor decryptor;
- CryptoAPIEncryptor encryptor;
-
public CryptoAPIEncryptionInfoBuilder() {
}
/**
* initialize the builder from a stream
*/
+ @Override
public void initialize(EncryptionInfo info, LittleEndianInput dis)
throws IOException {
- this.info = info;
/* int hSize = */ dis.readInt();
- header = new CryptoAPIEncryptionHeader(dis);
- verifier = new CryptoAPIEncryptionVerifier(dis, header);
- decryptor = new CryptoAPIDecryptor(this);
- encryptor = new CryptoAPIEncryptor(this);
+ CryptoAPIEncryptionHeader header = new CryptoAPIEncryptionHeader(dis);
+ info.setHeader(header);
+ info.setVerifier(new CryptoAPIEncryptionVerifier(dis, header));
+ CryptoAPIDecryptor dec = new CryptoAPIDecryptor();
+ dec.setEncryptionInfo(info);
+ info.setDecryptor(dec);
+ CryptoAPIEncryptor enc = new CryptoAPIEncryptor();
+ enc.setEncryptionInfo(info);
+ info.setEncryptor(enc);
}
/**
* initialize the builder from scratch
*/
+ @Override
public void initialize(EncryptionInfo info,
CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm,
int keyBits, int blockSize, ChainingMode chainingMode) {
- this.info = info;
- if (cipherAlgorithm == null) cipherAlgorithm = CipherAlgorithm.rc4;
- if (hashAlgorithm == null) hashAlgorithm = HashAlgorithm.sha1;
- if (keyBits == -1) keyBits = 0x28;
+ if (cipherAlgorithm == null) {
+ cipherAlgorithm = CipherAlgorithm.rc4;
+ }
+ if (hashAlgorithm == null) {
+ hashAlgorithm = HashAlgorithm.sha1;
+ }
+ if (keyBits == -1) {
+ keyBits = 0x28;
+ }
assert(cipherAlgorithm == CipherAlgorithm.rc4 && hashAlgorithm == HashAlgorithm.sha1);
- header = new CryptoAPIEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
- verifier = new CryptoAPIEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
- decryptor = new CryptoAPIDecryptor(this);
- encryptor = new CryptoAPIEncryptor(this);
- }
-
- public CryptoAPIEncryptionHeader getHeader() {
- return header;
- }
-
- public CryptoAPIEncryptionVerifier getVerifier() {
- return verifier;
- }
-
- public CryptoAPIDecryptor getDecryptor() {
- return decryptor;
- }
-
- public CryptoAPIEncryptor getEncryptor() {
- return encryptor;
- }
-
- public EncryptionInfo getEncryptionInfo() {
- return info;
+ info.setHeader(new CryptoAPIEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
+ info.setVerifier(new CryptoAPIEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
+ CryptoAPIDecryptor dec = new CryptoAPIDecryptor();
+ dec.setEncryptionInfo(info);
+ info.setDecryptor(dec);
+ CryptoAPIEncryptor enc = new CryptoAPIEncryptor();
+ enc.setEncryptionInfo(info);
+ info.setEncryptor(enc);
}
}
diff --git a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionVerifier.java b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionVerifier.java
index 160d1f9f91..d2c87b7ab9 100644
--- a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionVerifier.java
+++ b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionVerifier.java
@@ -23,7 +23,7 @@ import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.crypt.standard.StandardEncryptionVerifier;
import org.apache.poi.util.LittleEndianInput;
-public class CryptoAPIEncryptionVerifier extends StandardEncryptionVerifier {
+public class CryptoAPIEncryptionVerifier extends StandardEncryptionVerifier implements Cloneable {
protected CryptoAPIEncryptionVerifier(LittleEndianInput is,
CryptoAPIEncryptionHeader header) {
@@ -36,15 +36,23 @@ public class CryptoAPIEncryptionVerifier extends StandardEncryptionVerifier {
super(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
}
+ @Override
protected void setSalt(byte salt[]) {
super.setSalt(salt);
}
+ @Override
protected void setEncryptedVerifier(byte encryptedVerifier[]) {
super.setEncryptedVerifier(encryptedVerifier);
}
+ @Override
protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) {
super.setEncryptedVerifierHash(encryptedVerifierHash);
}
+
+ @Override
+ public CryptoAPIEncryptionVerifier clone() throws CloneNotSupportedException {
+ return (CryptoAPIEncryptionVerifier)super.clone();
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptor.java b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptor.java
index 47d4696cbf..02d28761ac 100644
--- a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptor.java
+++ b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptor.java
@@ -18,7 +18,7 @@
package org.apache.poi.poifs.crypt.cryptoapi;
import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
+import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
@@ -36,6 +36,7 @@ import org.apache.poi.hpsf.DocumentSummaryInformation;
import org.apache.poi.hpsf.PropertySetFactory;
import org.apache.poi.hpsf.SummaryInformation;
import org.apache.poi.hpsf.WritingNotSupportedException;
+import org.apache.poi.poifs.crypt.ChunkedCipherOutputStream;
import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.poifs.crypt.DataSpaceMapUtils;
import org.apache.poi.poifs.crypt.EncryptionInfo;
@@ -50,13 +51,14 @@ import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
import org.apache.poi.util.StringUtil;
-public class CryptoAPIEncryptor extends Encryptor {
- private final CryptoAPIEncryptionInfoBuilder builder;
-
- protected CryptoAPIEncryptor(CryptoAPIEncryptionInfoBuilder builder) {
- this.builder = builder;
+public class CryptoAPIEncryptor extends Encryptor implements Cloneable {
+
+ private int _chunkSize = 512;
+
+ protected CryptoAPIEncryptor() {
}
+ @Override
public void confirmPassword(String password) {
Random r = new SecureRandom();
byte salt[] = new byte[16];
@@ -66,11 +68,12 @@ public class CryptoAPIEncryptor extends Encryptor {
confirmPassword(password, null, null, verifier, salt, null);
}
+ @Override
public void confirmPassword(String password, byte keySpec[],
byte keySalt[], byte verifier[], byte verifierSalt[],
byte integritySalt[]) {
assert(verifier != null && verifierSalt != null);
- CryptoAPIEncryptionVerifier ver = builder.getVerifier();
+ CryptoAPIEncryptionVerifier ver = (CryptoAPIEncryptionVerifier)getEncryptionInfo().getVerifier();
ver.setSalt(verifierSalt);
SecretKey skey = CryptoAPIDecryptor.generateSecretKey(password, ver);
setSecretKey(skey);
@@ -99,8 +102,19 @@ public class CryptoAPIEncryptor extends Encryptor {
*/
public Cipher initCipherForBlock(Cipher cipher, int block)
throws GeneralSecurityException {
- return CryptoAPIDecryptor.initCipherForBlock(cipher, block, builder, getSecretKey(), Cipher.ENCRYPT_MODE);
+ return CryptoAPIDecryptor.initCipherForBlock(cipher, block, getEncryptionInfo(), getSecretKey(), Cipher.ENCRYPT_MODE);
}
+
+ @Override
+ public ChunkedCipherOutputStream getDataStream(DirectoryNode dir)
+ throws IOException, GeneralSecurityException {
+ throw new IOException("not supported");
+ }
+
+ public CryptoAPICipherOutputStream getDataStream(OutputStream stream)
+ throws IOException, GeneralSecurityException {
+ return new CryptoAPICipherOutputStream(stream);
+ }
/**
* Encrypt the Document-/SummaryInformation and other optionally streams.
@@ -109,9 +123,9 @@ public class CryptoAPIEncryptor extends Encryptor {
*
* @see 2.3.5.4 RC4 CryptoAPI Encrypted Summary Stream
*/
- public OutputStream getDataStream(DirectoryNode dir)
+ public OutputStream getSummaryEntries(DirectoryNode dir)
throws IOException, GeneralSecurityException {
- CipherByteArrayOutputStream bos = new CipherByteArrayOutputStream();
+ CryptoAPIDocumentOutputStream bos = new CryptoAPIDocumentOutputStream(this);
byte buf[] = new byte[8];
bos.write(buf, 0, 8); // skip header
@@ -124,7 +138,9 @@ public class CryptoAPIEncryptor extends Encryptor {
int block = 0;
for (String entryName : entryNames) {
- if (!dir.hasEntry(entryName)) continue;
+ if (!dir.hasEntry(entryName)) {
+ continue;
+ }
StreamDescriptorEntry descEntry = new StreamDescriptorEntry();
descEntry.block = block;
descEntry.streamOffset = bos.size();
@@ -193,15 +209,20 @@ public class CryptoAPIEncryptor extends Encryptor {
}
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 {
DataSpaceMapUtils.addDefaultDataSpace(dir);
- final EncryptionInfo info = builder.getEncryptionInfo();
- final CryptoAPIEncryptionHeader header = builder.getHeader();
- final CryptoAPIEncryptionVerifier verifier = builder.getVerifier();
+ final EncryptionInfo info = getEncryptionInfo();
+ final CryptoAPIEncryptionHeader header = (CryptoAPIEncryptionHeader)getEncryptionInfo().getHeader();
+ final CryptoAPIEncryptionVerifier verifier = (CryptoAPIEncryptionVerifier)getEncryptionInfo().getVerifier();
EncryptionRecord er = new EncryptionRecord() {
+ @Override
public void write(LittleEndianByteArrayOutputStream bos) {
bos.writeShort(info.getVersionMajor());
bos.writeShort(info.getVersionMinor());
@@ -212,44 +233,42 @@ public class CryptoAPIEncryptor extends Encryptor {
DataSpaceMapUtils.createEncryptionEntry(dir, "EncryptionInfo", er);
}
- private class CipherByteArrayOutputStream extends ByteArrayOutputStream {
- Cipher cipher;
- byte oneByte[] = { 0 };
-
- public CipherByteArrayOutputStream() throws GeneralSecurityException {
- setBlock(0);
- }
-
- public byte[] getBuf() {
- return buf;
- }
-
- public void setSize(int count) {
- this.count = count;
- }
-
- public void setBlock(int block) throws GeneralSecurityException {
- cipher = initCipherForBlock(cipher, block);
- }
-
- public void write(int b) {
- try {
- oneByte[0] = (byte)b;
- cipher.update(oneByte, 0, 1, oneByte, 0);
- super.write(oneByte);
- } catch (Exception e) {
- throw new EncryptedDocumentException(e);
- }
- }
-
- public void write(byte[] b, int off, int len) {
- try {
- cipher.update(b, off, len, b, off);
- super.write(b, off, len);
- } catch (Exception e) {
- throw new EncryptedDocumentException(e);
- }
- }
+ @Override
+ public CryptoAPIEncryptor clone() throws CloneNotSupportedException {
+ return (CryptoAPIEncryptor)super.clone();
}
+
+ protected class CryptoAPICipherOutputStream extends ChunkedCipherOutputStream {
+
+ @Override
+ protected Cipher initCipherForBlock(Cipher cipher, int block, boolean lastChunk)
+ throws IOException, GeneralSecurityException {
+ flush();
+ EncryptionInfo ei = getEncryptionInfo();
+ SecretKey sk = getSecretKey();
+ return CryptoAPIDecryptor.initCipherForBlock(cipher, block, ei, sk, Cipher.ENCRYPT_MODE);
+ }
+
+ @Override
+ protected void calculateChecksum(File file, int i) {
+ }
+
+ @Override
+ protected void createEncryptionInfoEntry(DirectoryNode dir, File tmpFile)
+ throws IOException, GeneralSecurityException {
+ throw new RuntimeException("createEncryptionInfoEntry not supported");
+ }
+
+ public CryptoAPICipherOutputStream(OutputStream stream)
+ throws IOException, GeneralSecurityException {
+ super(stream, CryptoAPIEncryptor.this._chunkSize);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ writeChunk(false);
+ }
+ }
+
}
diff --git a/src/java/org/apache/poi/poifs/crypt/standard/StandardDecryptor.java b/src/java/org/apache/poi/poifs/crypt/standard/StandardDecryptor.java
index a6d6dbfa0b..d06f9a3738 100644
--- a/src/java/org/apache/poi/poifs/crypt/standard/StandardDecryptor.java
+++ b/src/java/org/apache/poi/poifs/crypt/standard/StandardDecryptor.java
@@ -34,7 +34,6 @@ import org.apache.poi.poifs.crypt.ChainingMode;
import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.poifs.crypt.Decryptor;
import org.apache.poi.poifs.crypt.EncryptionHeader;
-import org.apache.poi.poifs.crypt.EncryptionInfoBuilder;
import org.apache.poi.poifs.crypt.EncryptionVerifier;
import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.filesystem.DirectoryNode;
@@ -44,15 +43,15 @@ import org.apache.poi.util.LittleEndian;
/**
*/
-public class StandardDecryptor extends Decryptor {
+public class StandardDecryptor extends Decryptor implements Cloneable {
private long _length = -1;
- protected StandardDecryptor(EncryptionInfoBuilder builder) {
- super(builder);
+ protected StandardDecryptor() {
}
+ @Override
public boolean verifyPassword(String password) {
- EncryptionVerifier ver = builder.getVerifier();
+ EncryptionVerifier ver = getEncryptionInfo().getVerifier();
SecretKey skey = generateSecretKey(password, ver, getKeySizeInBytes());
Cipher cipher = getCipher(skey);
@@ -116,12 +115,13 @@ public class StandardDecryptor extends Decryptor {
}
private Cipher getCipher(SecretKey key) {
- EncryptionHeader em = builder.getHeader();
+ EncryptionHeader em = getEncryptionInfo().getHeader();
ChainingMode cm = em.getChainingMode();
assert(cm == ChainingMode.ecb);
return CryptoFunctions.getCipher(key, em.getCipherAlgorithm(), cm, null, Cipher.DECRYPT_MODE);
}
+ @Override
@SuppressWarnings("resource")
public InputStream getDataStream(DirectoryNode dir) throws IOException {
DocumentInputStream dis = dir.createDocumentInputStream(DEFAULT_POIFS_ENTRY);
@@ -134,7 +134,7 @@ public class StandardDecryptor extends Decryptor {
// limit wrong calculated ole entries - (bug #57080)
// standard encryption always uses aes encoding, so blockSize is always 16
// http://stackoverflow.com/questions/3283787/size-of-data-after-aes-encryption
- int blockSize = builder.getHeader().getCipherAlgorithm().blockSize;
+ int blockSize = getEncryptionInfo().getHeader().getCipherAlgorithm().blockSize;
long cipherLen = (_length/blockSize + 1) * blockSize;
Cipher cipher = getCipher(getSecretKey());
@@ -145,8 +145,16 @@ public class StandardDecryptor extends Decryptor {
/**
* @return the length of the stream returned by {@link #getDataStream(DirectoryNode)}
*/
+ @Override
public long getLength(){
- if(_length == -1) throw new IllegalStateException("Decryptor.getDataStream() was not called");
+ if(_length == -1) {
+ throw new IllegalStateException("Decryptor.getDataStream() was not called");
+ }
return _length;
}
+
+ @Override
+ public StandardDecryptor clone() throws CloneNotSupportedException {
+ return (StandardDecryptor)super.clone();
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionHeader.java b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionHeader.java
index 44d7bc5953..30f35581f7 100644
--- a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionHeader.java
+++ b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionHeader.java
@@ -22,6 +22,7 @@ import static org.apache.poi.poifs.crypt.EncryptionInfo.flagCryptoAPI;
import java.io.IOException;
import java.io.InputStream;
+import org.apache.poi.hssf.record.RecordInputStream;
import org.apache.poi.poifs.crypt.ChainingMode;
import org.apache.poi.poifs.crypt.CipherAlgorithm;
import org.apache.poi.poifs.crypt.CipherProvider;
@@ -33,7 +34,7 @@ import org.apache.poi.util.LittleEndianInput;
import org.apache.poi.util.LittleEndianOutput;
import org.apache.poi.util.StringUtil;
-public class StandardEncryptionHeader extends EncryptionHeader implements EncryptionRecord {
+public class StandardEncryptionHeader extends EncryptionHeader implements EncryptionRecord, Cloneable {
protected StandardEncryptionHeader(LittleEndianInput is) throws IOException {
setFlags(is.readInt());
@@ -55,9 +56,17 @@ public class StandardEncryptionHeader extends EncryptionHeader implements Encryp
// CSPName may not always be specified
// In some cases, the salt value of the EncryptionVerifier is the next chunk of data
- ((InputStream)is).mark(LittleEndianConsts.INT_SIZE+1);
+ if (is instanceof RecordInputStream) {
+ ((RecordInputStream)is).mark(LittleEndianConsts.INT_SIZE+1);
+ } else {
+ ((InputStream)is).mark(LittleEndianConsts.INT_SIZE+1);
+ }
int checkForSalt = is.readInt();
- ((InputStream)is).reset();
+ if (is instanceof RecordInputStream) {
+ ((RecordInputStream)is).reset();
+ } else {
+ ((InputStream)is).reset();
+ }
if (checkForSalt == 16) {
setCspName("");
@@ -65,7 +74,9 @@ public class StandardEncryptionHeader extends EncryptionHeader implements Encryp
StringBuilder builder = new StringBuilder();
while (true) {
char c = (char) is.readShort();
- if (c == 0) break;
+ if (c == 0) {
+ break;
+ }
builder.append(c);
}
setCspName(builder.toString());
@@ -90,6 +101,7 @@ public class StandardEncryptionHeader extends EncryptionHeader implements Encryp
/**
* serializes the header
*/
+ @Override
public void write(LittleEndianByteArrayOutputStream bos) {
int startIdx = bos.getWriteIndex();
LittleEndianOutput sizeOutput = bos.createDelayedOutput(LittleEndianConsts.INT_SIZE);
@@ -102,10 +114,17 @@ public class StandardEncryptionHeader extends EncryptionHeader implements Encryp
bos.writeInt(0); // reserved1
bos.writeInt(0); // reserved2
String cspName = getCspName();
- if (cspName == null) cspName = getCipherProvider().cipherProviderName;
+ if (cspName == null) {
+ cspName = getCipherProvider().cipherProviderName;
+ }
bos.write(StringUtil.getToUnicodeLE(cspName));
bos.writeShort(0);
int headerSize = bos.getWriteIndex()-startIdx-LittleEndianConsts.INT_SIZE;
sizeOutput.writeInt(headerSize);
}
+
+ @Override
+ public StandardEncryptionHeader clone() throws CloneNotSupportedException {
+ return (StandardEncryptionHeader)super.clone();
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionInfoBuilder.java b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionInfoBuilder.java
index d55c8e0d6f..c2bffdd05a 100644
--- a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionInfoBuilder.java
+++ b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionInfoBuilder.java
@@ -27,34 +27,29 @@ import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.util.LittleEndianInput;
public class StandardEncryptionInfoBuilder implements EncryptionInfoBuilder {
-
- EncryptionInfo info;
- StandardEncryptionHeader header;
- StandardEncryptionVerifier verifier;
- StandardDecryptor decryptor;
- StandardEncryptor encryptor;
/**
* initialize the builder from a stream
*/
+ @Override
public void initialize(EncryptionInfo info, LittleEndianInput dis) throws IOException {
- this.info = info;
-
/* int hSize = */ dis.readInt();
- header = new StandardEncryptionHeader(dis);
- verifier = new StandardEncryptionVerifier(dis, header);
+ StandardEncryptionHeader header = new StandardEncryptionHeader(dis);
+ info.setHeader(header);
+ info.setVerifier(new StandardEncryptionVerifier(dis, header));
if (info.getVersionMinor() == 2 && (info.getVersionMajor() == 3 || info.getVersionMajor() == 4)) {
- decryptor = new StandardDecryptor(this);
+ StandardDecryptor dec = new StandardDecryptor();
+ dec.setEncryptionInfo(info);
+ info.setDecryptor(dec);
}
}
/**
* initialize the builder from scratch
*/
+ @Override
public void initialize(EncryptionInfo info, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
- this.info = info;
-
if (cipherAlgorithm == null) {
cipherAlgorithm = CipherAlgorithm.aes128;
}
@@ -89,29 +84,13 @@ public class StandardEncryptionInfoBuilder implements EncryptionInfoBuilder {
if (!found) {
throw new EncryptedDocumentException("KeySize "+keyBits+" not allowed for Cipher "+cipherAlgorithm.toString());
}
- header = new StandardEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
- verifier = new StandardEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
- decryptor = new StandardDecryptor(this);
- encryptor = new StandardEncryptor(this);
- }
-
- public StandardEncryptionHeader getHeader() {
- return header;
- }
-
- public StandardEncryptionVerifier getVerifier() {
- return verifier;
- }
-
- public StandardDecryptor getDecryptor() {
- return decryptor;
- }
-
- public StandardEncryptor getEncryptor() {
- return encryptor;
- }
-
- public EncryptionInfo getEncryptionInfo() {
- return info;
+ info.setHeader(new StandardEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
+ info.setVerifier(new StandardEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
+ StandardDecryptor dec = new StandardDecryptor();
+ dec.setEncryptionInfo(info);
+ info.setDecryptor(dec);
+ StandardEncryptor enc = new StandardEncryptor();
+ enc.setEncryptionInfo(info);
+ info.setEncryptor(enc);
}
}
diff --git a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionVerifier.java b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionVerifier.java
index 021d82f9eb..f00efecfb6 100644
--- a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionVerifier.java
+++ b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionVerifier.java
@@ -27,7 +27,7 @@ import org.apache.poi.util.LittleEndianInput;
/**
* Used when checking if a key is valid for a document
*/
-public class StandardEncryptionVerifier extends EncryptionVerifier implements EncryptionRecord {
+public class StandardEncryptionVerifier extends EncryptionVerifier implements EncryptionRecord, Cloneable {
private static final int SPIN_COUNT = 50000;
private final int verifierHashSize;
@@ -68,6 +68,7 @@ public class StandardEncryptionVerifier extends EncryptionVerifier implements En
}
// make method visible for this package
+ @Override
protected void setSalt(byte salt[]) {
if (salt == null || salt.length != 16) {
throw new EncryptedDocumentException("invalid verifier salt");
@@ -76,15 +77,18 @@ public class StandardEncryptionVerifier extends EncryptionVerifier implements En
}
// make method visible for this package
+ @Override
protected void setEncryptedVerifier(byte encryptedVerifier[]) {
super.setEncryptedVerifier(encryptedVerifier);
}
// make method visible for this package
+ @Override
protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) {
super.setEncryptedVerifierHash(encryptedVerifierHash);
}
+ @Override
public void write(LittleEndianByteArrayOutputStream bos) {
// see [MS-OFFCRYPTO] - 2.3.4.9
byte salt[] = getSalt();
@@ -115,4 +119,9 @@ public class StandardEncryptionVerifier extends EncryptionVerifier implements En
protected int getVerifierHashSize() {
return verifierHashSize;
}
+
+ @Override
+ public StandardEncryptionVerifier clone() throws CloneNotSupportedException {
+ return (StandardEncryptionVerifier)super.clone();
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptor.java b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptor.java
index fded6c6061..5372c2af8e 100644
--- a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptor.java
+++ b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptor.java
@@ -53,15 +53,13 @@ import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.TempFile;
-public class StandardEncryptor extends Encryptor {
+public class StandardEncryptor extends Encryptor implements Cloneable {
private static final POILogger logger = POILogFactory.getLogger(StandardEncryptor.class);
- private final StandardEncryptionInfoBuilder builder;
-
- protected StandardEncryptor(StandardEncryptionInfoBuilder builder) {
- this.builder = builder;
+ protected StandardEncryptor() {
}
+ @Override
public void confirmPassword(String password) {
// see [MS-OFFCRYPTO] - 2.3.3 EncryptionVerifier
Random r = new SecureRandom();
@@ -79,8 +77,9 @@ public class StandardEncryptor extends Encryptor {
*
* see [MS-OFFCRYPTO] - 2.3.4.7 ECMA-376 Document Encryption Key Generation
*/
+ @Override
public void confirmPassword(String password, byte keySpec[], byte keySalt[], byte verifier[], byte verifierSalt[], byte integritySalt[]) {
- StandardEncryptionVerifier ver = builder.getVerifier();
+ StandardEncryptionVerifier ver = (StandardEncryptionVerifier)getEncryptionInfo().getVerifier();
ver.setSalt(verifierSalt);
SecretKey secretKey = generateSecretKey(password, ver, getKeySizeInBytes());
@@ -111,10 +110,11 @@ public class StandardEncryptor extends Encryptor {
}
private Cipher getCipher(SecretKey key, String padding) {
- EncryptionVerifier ver = builder.getVerifier();
+ EncryptionVerifier ver = getEncryptionInfo().getVerifier();
return CryptoFunctions.getCipher(key, ver.getCipherAlgorithm(), ver.getChainingMode(), null, Cipher.ENCRYPT_MODE, padding);
}
+ @Override
public OutputStream getDataStream(final DirectoryNode dir)
throws IOException, GeneralSecurityException {
createEncryptionInfoEntry(dir);
@@ -163,6 +163,7 @@ public class StandardEncryptor extends Encryptor {
countBytes++;
}
+ @Override
public void close() throws IOException {
// the CipherOutputStream adds the padding bytes on close()
super.close();
@@ -175,6 +176,7 @@ public class StandardEncryptor extends Encryptor {
// TODO: any properties???
}
+ @Override
public void processPOIFSWriterEvent(POIFSWriterEvent event) {
try {
LittleEndianOutputStream leos = new LittleEndianOutputStream(event.getStream());
@@ -200,15 +202,16 @@ public class StandardEncryptor extends Encryptor {
}
protected int getKeySizeInBytes() {
- return builder.getHeader().getKeySize()/8;
+ return getEncryptionInfo().getHeader().getKeySize()/8;
}
protected void createEncryptionInfoEntry(DirectoryNode dir) throws IOException {
- final EncryptionInfo info = builder.getEncryptionInfo();
- final StandardEncryptionHeader header = builder.getHeader();
- final StandardEncryptionVerifier verifier = builder.getVerifier();
+ final EncryptionInfo info = getEncryptionInfo();
+ final StandardEncryptionHeader header = (StandardEncryptionHeader)info.getHeader();
+ final StandardEncryptionVerifier verifier = (StandardEncryptionVerifier)info.getVerifier();
EncryptionRecord er = new EncryptionRecord(){
+ @Override
public void write(LittleEndianByteArrayOutputStream bos) {
bos.writeShort(info.getVersionMajor());
bos.writeShort(info.getVersionMinor());
@@ -222,4 +225,9 @@ public class StandardEncryptor extends Encryptor {
// TODO: any properties???
}
+
+ @Override
+ public StandardEncryptor clone() throws CloneNotSupportedException {
+ return (StandardEncryptor)super.clone();
+ }
}
diff --git a/src/java/org/apache/poi/util/LittleEndianByteArrayInputStream.java b/src/java/org/apache/poi/util/LittleEndianByteArrayInputStream.java
index 54388e1518..4594343bf1 100644
--- a/src/java/org/apache/poi/util/LittleEndianByteArrayInputStream.java
+++ b/src/java/org/apache/poi/util/LittleEndianByteArrayInputStream.java
@@ -17,103 +17,91 @@
package org.apache.poi.util;
+import java.io.ByteArrayInputStream;
+
/**
* Adapts a plain byte array to {@link LittleEndianInput}
- *
- * @author Josh Micich
*/
-public final class LittleEndianByteArrayInputStream implements LittleEndianInput {
- private final byte[] _buf;
- private final int _endIndex;
- private int _readIndex;
-
+public final class LittleEndianByteArrayInputStream extends ByteArrayInputStream implements LittleEndianInput {
public LittleEndianByteArrayInputStream(byte[] buf, int startOffset, int maxReadLen) { // NOSONAR
- _buf = buf;
- _readIndex = startOffset;
- _endIndex = startOffset + maxReadLen;
+ super(buf, startOffset, maxReadLen);
}
+
public LittleEndianByteArrayInputStream(byte[] buf, int startOffset) {
- this(buf, startOffset, buf.length - startOffset);
+ super(buf, startOffset, buf.length - startOffset);
}
+
public LittleEndianByteArrayInputStream(byte[] buf) {
- this(buf, 0, buf.length);
+ super(buf);
}
- public int available() {
- return _endIndex - _readIndex;
- }
private void checkPosition(int i) {
- if (i > _endIndex - _readIndex) {
+ if (i > count - pos) {
throw new RuntimeException("Buffer overrun");
}
}
public int getReadIndex() {
- return _readIndex;
+ return pos;
}
- public byte readByte() {
+
+ @Override
+ public byte readByte() {
checkPosition(1);
- return _buf[_readIndex++];
+ return (byte)read();
}
- public int readInt() {
- checkPosition(4);
- int i = _readIndex;
-
- int b0 = _buf[i++] & 0xFF;
- int b1 = _buf[i++] & 0xFF;
- int b2 = _buf[i++] & 0xFF;
- int b3 = _buf[i++] & 0xFF;
- _readIndex = i;
- return (b3 << 24) + (b2 << 16) + (b1 << 8) + (b0 << 0);
+ @Override
+ public int readInt() {
+ final int size = LittleEndianConsts.INT_SIZE;
+ checkPosition(size);
+ int le = LittleEndian.getInt(buf, pos);
+ super.skip(size);
+ return le;
}
- public long readLong() {
- checkPosition(8);
- int i = _readIndex;
- int b0 = _buf[i++] & 0xFF;
- int b1 = _buf[i++] & 0xFF;
- int b2 = _buf[i++] & 0xFF;
- int b3 = _buf[i++] & 0xFF;
- int b4 = _buf[i++] & 0xFF;
- int b5 = _buf[i++] & 0xFF;
- int b6 = _buf[i++] & 0xFF;
- int b7 = _buf[i++] & 0xFF;
- _readIndex = i;
- return (((long)b7 << 56) +
- ((long)b6 << 48) +
- ((long)b5 << 40) +
- ((long)b4 << 32) +
- ((long)b3 << 24) +
- (b2 << 16) +
- (b1 << 8) +
- (b0 << 0));
+ @Override
+ public long readLong() {
+ final int size = LittleEndianConsts.LONG_SIZE;
+ checkPosition(size);
+ long le = LittleEndian.getLong(buf, pos);
+ super.skip(size);
+ return le;
}
- public short readShort() {
+
+ @Override
+ public short readShort() {
return (short)readUShort();
}
- public int readUByte() {
- checkPosition(1);
- return _buf[_readIndex++] & 0xFF;
- }
- public int readUShort() {
- checkPosition(2);
- int i = _readIndex;
- int b0 = _buf[i++] & 0xFF;
- int b1 = _buf[i++] & 0xFF;
- _readIndex = i;
- return (b1 << 8) + (b0 << 0);
+ @Override
+ public int readUByte() {
+ return readByte() & 0xFF;
}
- public void readFully(byte[] buf, int off, int len) {
+
+ @Override
+ public int readUShort() {
+ final int size = LittleEndianConsts.SHORT_SIZE;
+ checkPosition(size);
+ int le = LittleEndian.getUShort(buf, pos);
+ super.skip(size);
+ return le;
+ }
+
+ @Override
+ public double readDouble() {
+ return Double.longBitsToDouble(readLong());
+ }
+
+ @Override
+ public void readFully(byte[] buffer, int off, int len) {
checkPosition(len);
- System.arraycopy(_buf, _readIndex, buf, off, len);
- _readIndex+=len;
+ read(buffer, off, len);
}
- public void readFully(byte[] buf) {
- readFully(buf, 0, buf.length);
- }
- public double readDouble() {
- return Double.longBitsToDouble(readLong());
+
+ @Override
+ public void readFully(byte[] buffer) {
+ checkPosition(buffer.length);
+ read(buffer, 0, buffer.length);
}
}
diff --git a/src/java/org/apache/poi/util/LittleEndianByteArrayOutputStream.java b/src/java/org/apache/poi/util/LittleEndianByteArrayOutputStream.java
index 081309cc2e..fe9949f1af 100644
--- a/src/java/org/apache/poi/util/LittleEndianByteArrayOutputStream.java
+++ b/src/java/org/apache/poi/util/LittleEndianByteArrayOutputStream.java
@@ -17,28 +17,26 @@
package org.apache.poi.util;
+import java.io.OutputStream;
/**
- * Adapts a plain byte array to {@link LittleEndianOutput}
- *
- *
- * @author Josh Micich
+ * Adapts a plain byte array to {@link LittleEndianOutput}
*/
-public final class LittleEndianByteArrayOutputStream implements LittleEndianOutput, DelayableLittleEndianOutput {
+public final class LittleEndianByteArrayOutputStream extends OutputStream implements LittleEndianOutput, DelayableLittleEndianOutput {
private final byte[] _buf;
private final int _endIndex;
private int _writeIndex;
public LittleEndianByteArrayOutputStream(byte[] buf, int startOffset, int maxWriteLen) { // NOSONAR
if (startOffset < 0 || startOffset > buf.length) {
- throw new IllegalArgumentException("Specified startOffset (" + startOffset
+ throw new IllegalArgumentException("Specified startOffset (" + startOffset
+ ") is out of allowable range (0.." + buf.length + ")");
}
_buf = buf;
_writeIndex = startOffset;
_endIndex = startOffset + maxWriteLen;
if (_endIndex < startOffset || _endIndex > buf.length) {
- throw new IllegalArgumentException("calculated end index (" + _endIndex
+ throw new IllegalArgumentException("calculated end index (" + _endIndex
+ ") is out of allowable range (" + _writeIndex + ".." + buf.length + ")");
}
}
@@ -52,16 +50,19 @@ public final class LittleEndianByteArrayOutputStream implements LittleEndianOutp
}
}
- public void writeByte(int v) {
+ @Override
+ public void writeByte(int v) {
checkPosition(1);
_buf[_writeIndex++] = (byte)v;
}
- public void writeDouble(double v) {
+ @Override
+ public void writeDouble(double v) {
writeLong(Double.doubleToLongBits(v));
}
- public void writeInt(int v) {
+ @Override
+ public void writeInt(int v) {
checkPosition(4);
int i = _writeIndex;
_buf[i++] = (byte)((v >>> 0) & 0xFF);
@@ -71,33 +72,47 @@ public final class LittleEndianByteArrayOutputStream implements LittleEndianOutp
_writeIndex = i;
}
- public void writeLong(long v) {
+ @Override
+ public void writeLong(long v) {
writeInt((int)(v >> 0));
writeInt((int)(v >> 32));
}
- public void writeShort(int v) {
+ @Override
+ public void writeShort(int v) {
checkPosition(2);
int i = _writeIndex;
_buf[i++] = (byte)((v >>> 0) & 0xFF);
_buf[i++] = (byte)((v >>> 8) & 0xFF);
_writeIndex = i;
}
- public void write(byte[] b) {
+
+ @Override
+ public void write(int b) {
+ writeByte(b);
+ }
+
+ @Override
+ public void write(byte[] b) {
int len = b.length;
checkPosition(len);
System.arraycopy(b, 0, _buf, _writeIndex, len);
_writeIndex += len;
}
- public void write(byte[] b, int offset, int len) {
+
+ @Override
+ public void write(byte[] b, int offset, int len) {
checkPosition(len);
System.arraycopy(b, offset, _buf, _writeIndex, len);
_writeIndex += len;
}
+
public int getWriteIndex() {
return _writeIndex;
}
- public LittleEndianOutput createDelayedOutput(int size) {
+
+ @Override
+ public LittleEndianOutput createDelayedOutput(int size) {
checkPosition(size);
LittleEndianOutput result = new LittleEndianByteArrayOutputStream(_buf, _writeIndex, size);
_writeIndex += size;
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java
index f8c9bb551c..480137ef8a 100644
--- a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java
@@ -45,7 +45,7 @@ import org.apache.poi.poifs.crypt.CipherAlgorithm;
import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.poifs.crypt.Decryptor;
import org.apache.poi.poifs.crypt.EncryptionHeader;
-import org.apache.poi.poifs.crypt.EncryptionInfoBuilder;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
import org.apache.poi.poifs.crypt.EncryptionVerifier;
import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.crypt.agile.AgileEncryptionVerifier.AgileCertificateEntry;
@@ -56,14 +56,14 @@ import org.apache.poi.util.LittleEndian;
/**
* Decryptor implementation for Agile Encryption
*/
-public class AgileDecryptor extends Decryptor {
+public class AgileDecryptor extends Decryptor implements Cloneable {
private long _length = -1;
- protected static final byte[] kVerifierInputBlock;
- protected static final byte[] kHashedVerifierBlock;
- protected static final byte[] kCryptoKeyBlock;
- protected static final byte[] kIntegrityKeyBlock;
- protected static final byte[] kIntegrityValueBlock;
+ /* package */ static final byte[] kVerifierInputBlock;
+ /* package */ static final byte[] kHashedVerifierBlock;
+ /* package */ static final byte[] kCryptoKeyBlock;
+ /* package */ static final byte[] kIntegrityKeyBlock;
+ /* package */ static final byte[] kIntegrityValueBlock;
static {
kVerifierInputBlock =
@@ -83,16 +83,16 @@ public class AgileDecryptor extends Decryptor {
(byte)0xb2, (byte)0x2c, (byte)0x84, (byte)0x33 };
}
- protected AgileDecryptor(AgileEncryptionInfoBuilder builder) {
- super(builder);
+ protected AgileDecryptor() {
}
/**
* set decryption password
*/
+ @Override
public boolean verifyPassword(String password) throws GeneralSecurityException {
- AgileEncryptionVerifier ver = (AgileEncryptionVerifier)builder.getVerifier();
- AgileEncryptionHeader header = (AgileEncryptionHeader)builder.getHeader();
+ AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
+ AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
HashAlgorithm hashAlgo = header.getHashAlgorithmEx();
CipherAlgorithm cipherAlgo = header.getCipherAlgorithm();
int blockSize = header.getBlockSize();
@@ -113,7 +113,7 @@ public class AgileDecryptor extends Decryptor {
* blockSize bytes.
* 4. Use base64 to encode the result of step 3.
*/
- byte verfierInputEnc[] = hashInput(builder, pwHash, kVerifierInputBlock, ver.getEncryptedVerifier(), Cipher.DECRYPT_MODE);
+ byte verfierInputEnc[] = hashInput(getEncryptionInfo(), pwHash, kVerifierInputBlock, ver.getEncryptedVerifier(), Cipher.DECRYPT_MODE);
setVerifier(verfierInputEnc);
MessageDigest hashMD = getMessageDigest(hashAlgo);
byte[] verifierHash = hashMD.digest(verfierInputEnc);
@@ -130,7 +130,7 @@ public class AgileDecryptor extends Decryptor {
* blockSize bytes, pad the hash value with 0x00 to an integral multiple of blockSize bytes.
* 4. Use base64 to encode the result of step 3.
*/
- byte verifierHashDec[] = hashInput(builder, pwHash, kHashedVerifierBlock, ver.getEncryptedVerifierHash(), Cipher.DECRYPT_MODE);
+ byte verifierHashDec[] = hashInput(getEncryptionInfo(), pwHash, kHashedVerifierBlock, ver.getEncryptedVerifierHash(), Cipher.DECRYPT_MODE);
verifierHashDec = getBlock0(verifierHashDec, hashAlgo.hashSize);
/**
@@ -146,7 +146,7 @@ public class AgileDecryptor extends Decryptor {
* blockSize bytes.
* 4. Use base64 to encode the result of step 3.
*/
- byte keyspec[] = hashInput(builder, pwHash, kCryptoKeyBlock, ver.getEncryptedKey(), Cipher.DECRYPT_MODE);
+ byte keyspec[] = hashInput(getEncryptionInfo(), pwHash, kCryptoKeyBlock, ver.getEncryptedKey(), Cipher.DECRYPT_MODE);
keyspec = getBlock0(keyspec, keySize);
SecretKeySpec secretKey = new SecretKeySpec(keyspec, ver.getCipherAlgorithm().jceId);
@@ -204,8 +204,8 @@ public class AgileDecryptor extends Decryptor {
* @throws GeneralSecurityException
*/
public boolean verifyPassword(KeyPair keyPair, X509Certificate x509) throws GeneralSecurityException {
- AgileEncryptionVerifier ver = (AgileEncryptionVerifier)builder.getVerifier();
- AgileEncryptionHeader header = (AgileEncryptionHeader)builder.getHeader();
+ AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
+ AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
HashAlgorithm hashAlgo = header.getHashAlgorithmEx();
CipherAlgorithm cipherAlgo = header.getCipherAlgorithm();
int blockSize = header.getBlockSize();
@@ -217,7 +217,9 @@ public class AgileDecryptor extends Decryptor {
break;
}
}
- if (ace == null) return false;
+ if (ace == null) {
+ return false;
+ }
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
@@ -255,9 +257,9 @@ public class AgileDecryptor extends Decryptor {
return fillSize;
}
- protected static byte[] hashInput(EncryptionInfoBuilder builder, byte pwHash[], byte blockKey[], byte inputKey[], int cipherMode) {
- EncryptionVerifier ver = builder.getVerifier();
- AgileDecryptor dec = (AgileDecryptor)builder.getDecryptor();
+ protected static byte[] hashInput(EncryptionInfo encryptionInfo, byte pwHash[], byte blockKey[], byte inputKey[], int cipherMode) {
+ EncryptionVerifier ver = encryptionInfo.getVerifier();
+ AgileDecryptor dec = (AgileDecryptor)encryptionInfo.getDecryptor();
int keySize = dec.getKeySizeInBytes();
int blockSize = dec.getBlockSizeInBytes();
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
@@ -278,6 +280,7 @@ public class AgileDecryptor extends Decryptor {
}
}
+ @Override
@SuppressWarnings("resource")
public InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException {
DocumentInputStream dis = dir.createDocumentInputStream(DEFAULT_POIFS_ENTRY);
@@ -285,17 +288,20 @@ public class AgileDecryptor extends Decryptor {
return new AgileCipherInputStream(dis, _length);
}
+ @Override
public long getLength(){
- if(_length == -1) throw new IllegalStateException("EcmaDecryptor.getDataStream() was not called");
+ if(_length == -1) {
+ throw new IllegalStateException("EcmaDecryptor.getDataStream() was not called");
+ }
return _length;
}
- protected static Cipher initCipherForBlock(Cipher existing, int block, boolean lastChunk, EncryptionInfoBuilder builder, SecretKey skey, int encryptionMode)
+ protected static Cipher initCipherForBlock(Cipher existing, int block, boolean lastChunk, EncryptionInfo encryptionInfo, SecretKey skey, int encryptionMode)
throws GeneralSecurityException {
- EncryptionHeader header = builder.getHeader();
- if (existing == null || lastChunk) {
- String padding = (lastChunk ? "PKCS5Padding" : "NoPadding");
+ EncryptionHeader header = encryptionInfo.getHeader();
+ String padding = (lastChunk ? "PKCS5Padding" : "NoPadding");
+ if (existing == null || !existing.getAlgorithm().endsWith(padding)) {
existing = getCipher(skey, header.getCipherAlgorithm(), header.getChainingMode(), header.getKeySalt(), encryptionMode, padding);
}
@@ -339,9 +345,15 @@ public class AgileDecryptor extends Decryptor {
// TODO: calculate integrity hmac while reading the stream
// for a post-validation of the data
+ @Override
protected Cipher initCipherForBlock(Cipher cipher, int block)
throws GeneralSecurityException {
- return AgileDecryptor.initCipherForBlock(cipher, block, false, builder, getSecretKey(), Cipher.DECRYPT_MODE);
+ return AgileDecryptor.initCipherForBlock(cipher, block, false, getEncryptionInfo(), getSecretKey(), Cipher.DECRYPT_MODE);
}
}
+
+ @Override
+ public AgileDecryptor clone() throws CloneNotSupportedException {
+ return (AgileDecryptor)super.clone();
+ }
}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionHeader.java b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionHeader.java
index a5fb144282..88bccabf6c 100644
--- a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionHeader.java
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionHeader.java
@@ -27,7 +27,7 @@ import com.microsoft.schemas.office.x2006.encryption.CTKeyData;
import com.microsoft.schemas.office.x2006.encryption.EncryptionDocument;
import com.microsoft.schemas.office.x2006.encryption.STCipherChaining;
-public class AgileEncryptionHeader extends EncryptionHeader {
+public class AgileEncryptionHeader extends EncryptionHeader implements Cloneable {
private byte encryptedHmacKey[], encryptedHmacValue[];
public AgileEncryptionHeader(String descriptor) {
@@ -99,6 +99,7 @@ public class AgileEncryptionHeader extends EncryptionHeader {
}
// make method visible for this package
+ @Override
protected void setKeySalt(byte salt[]) {
if (salt == null || salt.length != getBlockSize()) {
throw new EncryptedDocumentException("invalid verifier salt");
@@ -121,4 +122,13 @@ public class AgileEncryptionHeader extends EncryptionHeader {
protected void setEncryptedHmacValue(byte[] encryptedHmacValue) {
this.encryptedHmacValue = (encryptedHmacValue == null) ? null : encryptedHmacValue.clone();
}
+
+ @Override
+ public AgileEncryptionHeader clone() throws CloneNotSupportedException {
+ AgileEncryptionHeader other = (AgileEncryptionHeader)super.clone();
+ other.encryptedHmacKey = (encryptedHmacKey == null) ? null : encryptedHmacKey.clone();
+ other.encryptedHmacValue = (encryptedHmacValue == null) ? null : encryptedHmacValue.clone();
+ return other;
+ }
+
}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionInfoBuilder.java b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionInfoBuilder.java
index ecf7fe8e69..acfa01581d 100644
--- a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionInfoBuilder.java
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionInfoBuilder.java
@@ -35,30 +35,24 @@ import com.microsoft.schemas.office.x2006.encryption.EncryptionDocument;
public class AgileEncryptionInfoBuilder implements EncryptionInfoBuilder {
- EncryptionInfo info;
- AgileEncryptionHeader header;
- AgileEncryptionVerifier verifier;
- AgileDecryptor decryptor;
- AgileEncryptor encryptor;
-
@Override
- public void initialize(EncryptionInfo ei, LittleEndianInput dis) throws IOException {
- this.info = ei;
-
+ public void initialize(EncryptionInfo info, LittleEndianInput dis) throws IOException {
EncryptionDocument ed = parseDescriptor((InputStream)dis);
- header = new AgileEncryptionHeader(ed);
- verifier = new AgileEncryptionVerifier(ed);
- if (ei.getVersionMajor() == EncryptionMode.agile.versionMajor
- && ei.getVersionMinor() == EncryptionMode.agile.versionMinor) {
- decryptor = new AgileDecryptor(this);
- encryptor = new AgileEncryptor(this);
+ info.setHeader(new AgileEncryptionHeader(ed));
+ info.setVerifier(new AgileEncryptionVerifier(ed));
+ if (info.getVersionMajor() == EncryptionMode.agile.versionMajor
+ && info.getVersionMinor() == EncryptionMode.agile.versionMinor) {
+ AgileDecryptor dec = new AgileDecryptor();
+ dec.setEncryptionInfo(info);
+ info.setDecryptor(dec);
+ AgileEncryptor enc = new AgileEncryptor();
+ enc.setEncryptionInfo(info);
+ info.setEncryptor(enc);
}
}
@Override
- public void initialize(EncryptionInfo ei, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
- this.info = ei;
-
+ public void initialize(EncryptionInfo info, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
if (cipherAlgorithm == null) {
cipherAlgorithm = CipherAlgorithm.aes128;
}
@@ -87,30 +81,14 @@ public class AgileEncryptionInfoBuilder implements EncryptionInfoBuilder {
if (!found) {
throw new EncryptedDocumentException("KeySize "+keyBits+" not allowed for Cipher "+cipherAlgorithm.toString());
}
- header = new AgileEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
- verifier = new AgileEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
- decryptor = new AgileDecryptor(this);
- encryptor = new AgileEncryptor(this);
- }
-
- public AgileEncryptionHeader getHeader() {
- return header;
- }
-
- public AgileEncryptionVerifier getVerifier() {
- return verifier;
- }
-
- public AgileDecryptor getDecryptor() {
- return decryptor;
- }
-
- public AgileEncryptor getEncryptor() {
- return encryptor;
- }
-
- protected EncryptionInfo getInfo() {
- return info;
+ info.setHeader(new AgileEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
+ info.setVerifier(new AgileEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
+ AgileDecryptor dec = new AgileDecryptor();
+ dec.setEncryptionInfo(info);
+ info.setDecryptor(dec);
+ AgileEncryptor enc = new AgileEncryptor();
+ enc.setEncryptionInfo(info);
+ info.setEncryptor(enc);
}
protected static EncryptionDocument parseDescriptor(String descriptor) {
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionVerifier.java b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionVerifier.java
index e2910431aa..53d4cd6ed2 100644
--- a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionVerifier.java
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionVerifier.java
@@ -39,7 +39,7 @@ import com.microsoft.schemas.office.x2006.keyEncryptor.password.CTPasswordKeyEnc
/**
* Used when checking if a key is valid for a document
*/
-public class AgileEncryptionVerifier extends EncryptionVerifier {
+public class AgileEncryptionVerifier extends EncryptionVerifier implements Cloneable {
public static class AgileCertificateEntry {
X509Certificate x509;
@@ -87,8 +87,9 @@ public class AgileEncryptionVerifier extends EncryptionVerifier {
setEncryptedVerifierHash(keyData.getEncryptedVerifierHashValue());
int saltSize = keyData.getSaltSize();
- if (saltSize != getSalt().length)
+ if (saltSize != getSalt().length) {
throw new EncryptedDocumentException("Invalid salt size");
+ }
switch (keyData.getCipherChaining().intValue()) {
case STCipherChaining.INT_CHAINING_MODE_CBC:
@@ -101,7 +102,9 @@ public class AgileEncryptionVerifier extends EncryptionVerifier {
throw new EncryptedDocumentException("Unsupported chaining mode - "+keyData.getCipherChaining().toString());
}
- if (!encList.hasNext()) return;
+ if (!encList.hasNext()) {
+ return;
+ }
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
@@ -125,6 +128,7 @@ public class AgileEncryptionVerifier extends EncryptionVerifier {
setSpinCount(100000); // TODO: use parameter
}
+ @Override
protected void setSalt(byte salt[]) {
if (salt == null || salt.length != getCipherAlgorithm().blockSize) {
throw new EncryptedDocumentException("invalid verifier salt");
@@ -133,16 +137,19 @@ public class AgileEncryptionVerifier extends EncryptionVerifier {
}
// make method visible for this package
+ @Override
protected void setEncryptedVerifier(byte encryptedVerifier[]) {
super.setEncryptedVerifier(encryptedVerifier);
}
// make method visible for this package
+ @Override
protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) {
super.setEncryptedVerifierHash(encryptedVerifierHash);
}
// make method visible for this package
+ @Override
protected void setEncryptedKey(byte[] encryptedKey) {
super.setEncryptedKey(encryptedKey);
}
@@ -156,4 +163,12 @@ public class AgileEncryptionVerifier extends EncryptionVerifier {
public List