diff --git a/src/java/org/apache/poi/POIDocument.java b/src/java/org/apache/poi/POIDocument.java index 87c6a14f6..d742c5572 100644 --- a/src/java/org/apache/poi/POIDocument.java +++ b/src/java/org/apache/poi/POIDocument.java @@ -32,6 +32,7 @@ import org.apache.poi.hpsf.PropertySet; import org.apache.poi.hpsf.PropertySetFactory; import org.apache.poi.hpsf.SummaryInformation; import org.apache.poi.poifs.crypt.EncryptionInfo; +import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIDecryptor; import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.DocumentInputStream; import org.apache.poi.poifs.filesystem.NPOIFSFileSystem; @@ -59,6 +60,8 @@ public abstract class POIDocument implements Closeable { /* Have the property streams been read yet? (Only done on-demand) */ private boolean initialized = false; + + private static final String[] encryptedStreamNames = { "EncryptedSummary" }; /** * Constructs a POIDocument with the given directory node. @@ -195,13 +198,18 @@ public abstract class POIDocument implements Closeable { try { if (encryptionInfo != null) { step = "getting encrypted"; - InputStream is = encryptionInfo.getDecryptor().getDataStream(directory); - try { - encPoifs = new NPOIFSFileSystem(is); - dirNode = encPoifs.getRoot(); - } finally { - is.close(); + String encryptedStream = null; + for (String s : encryptedStreamNames) { + if (dirNode.hasEntry(s)) { + encryptedStream = s; + } } + if (encryptedStream == null) { + throw new EncryptedDocumentException("can't find matching encrypted property stream"); + } + CryptoAPIDecryptor dec = (CryptoAPIDecryptor)encryptionInfo.getDecryptor(); + encPoifs = dec.getSummaryEntries(dirNode, encryptedStream); + dirNode = encPoifs.getRoot(); } //directory can be null when creating new documents diff --git a/src/java/org/apache/poi/hssf/record/RecordInputStream.java b/src/java/org/apache/poi/hssf/record/RecordInputStream.java index f9212c798..8991eb770 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 7d695a1eb..255494d6a 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 f0b14bffc..573cbdeb6 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 bec436a88..41621853e 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 b6a0e2d0c..c52e87c62 100644 --- a/src/java/org/apache/poi/poifs/crypt/EncryptionHeader.java +++ b/src/java/org/apache/poi/poifs/crypt/EncryptionHeader.java @@ -16,12 +16,11 @@ ==================================================================== */ package org.apache.poi.poifs.crypt; - /** * Reads and processes OOXML Encryption Headers * The constants are largely based on ZIP constants. */ -public abstract class EncryptionHeader { +public abstract class EncryptionHeader implements Cloneable { public static final int ALGORITHM_RC4 = CipherAlgorithm.rc4.ecmaId; public static final int ALGORITHM_AES_128 = CipherAlgorithm.aes128.ecmaId; public static final int ALGORITHM_AES_192 = CipherAlgorithm.aes192.ecmaId; @@ -132,4 +131,11 @@ public abstract class EncryptionHeader { protected void setCspName(String cspName) { this.cspName = cspName; } + + @Override + public EncryptionHeader clone() throws CloneNotSupportedException { + EncryptionHeader other = (EncryptionHeader)super.clone(); + other.keySalt = (keySalt == null) ? null : keySalt.clone(); + return other; + } } diff --git a/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java b/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java index 780409e19..34b83cb8c 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 e36d44da9..24371dfdd 100644 --- a/src/java/org/apache/poi/poifs/crypt/EncryptionInfoBuilder.java +++ b/src/java/org/apache/poi/poifs/crypt/EncryptionInfoBuilder.java @@ -30,24 +30,4 @@ public interface EncryptionInfoBuilder { * initialize the builder from scratch */ void initialize(EncryptionInfo ei, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode); - - /** - * @return the header data - */ - EncryptionHeader getHeader(); - - /** - * @return the verifier data - */ - EncryptionVerifier getVerifier(); - - /** - * @return the decryptor - */ - Decryptor getDecryptor(); - - /** - * @return the encryptor - */ - Encryptor getEncryptor(); } diff --git a/src/java/org/apache/poi/poifs/crypt/EncryptionVerifier.java b/src/java/org/apache/poi/poifs/crypt/EncryptionVerifier.java index 2688b50f4..6e8059261 100644 --- a/src/java/org/apache/poi/poifs/crypt/EncryptionVerifier.java +++ b/src/java/org/apache/poi/poifs/crypt/EncryptionVerifier.java @@ -16,11 +16,10 @@ ==================================================================== */ package org.apache.poi.poifs.crypt; - /** * Used when checking if a key is valid for a document */ -public abstract class EncryptionVerifier { +public abstract class EncryptionVerifier implements Cloneable { private byte[] salt; private byte[] encryptedVerifier; private byte[] encryptedVerifierHash; @@ -105,5 +104,13 @@ public abstract class EncryptionVerifier { this.hashAlgorithm = hashAlgorithm; } - + @Override + public EncryptionVerifier clone() throws CloneNotSupportedException { + EncryptionVerifier other = (EncryptionVerifier)super.clone(); + other.salt = (salt == null) ? null : salt.clone(); + other.encryptedVerifier = (encryptedVerifier == null) ? null : encryptedVerifier.clone(); + other.encryptedVerifierHash = (encryptedVerifierHash == null) ? null : encryptedVerifierHash.clone(); + other.encryptedKey = (encryptedKey == null) ? null : encryptedKey.clone(); + return other; + } } diff --git a/src/java/org/apache/poi/poifs/crypt/Encryptor.java b/src/java/org/apache/poi/poifs/crypt/Encryptor.java index d40f8ae5d..d92fc3097 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 5fb39d921..1fdf3a982 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 1b811a103..b9022017b 100644 --- a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionHeader.java +++ b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionHeader.java @@ -24,8 +24,7 @@ import org.apache.poi.poifs.crypt.HashAlgorithm; import org.apache.poi.poifs.crypt.standard.EncryptionRecord; import org.apache.poi.util.LittleEndianByteArrayOutputStream; -public class BinaryRC4EncryptionHeader extends EncryptionHeader implements - EncryptionRecord { +public class BinaryRC4EncryptionHeader extends EncryptionHeader implements EncryptionRecord, Cloneable { protected BinaryRC4EncryptionHeader() { setCipherAlgorithm(CipherAlgorithm.rc4); @@ -39,6 +38,14 @@ public class BinaryRC4EncryptionHeader extends EncryptionHeader implements setChainingMode(null); } + @Override public void write(LittleEndianByteArrayOutputStream littleendianbytearrayoutputstream) { } + + @Override + public BinaryRC4EncryptionHeader clone() throws CloneNotSupportedException { + return (BinaryRC4EncryptionHeader)super.clone(); + } + + } diff --git a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionInfoBuilder.java b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionInfoBuilder.java index 10bf58d83..94ddde72a 100644 --- a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionInfoBuilder.java +++ b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionInfoBuilder.java @@ -23,55 +23,37 @@ import org.apache.poi.util.LittleEndianInput; public class BinaryRC4EncryptionInfoBuilder implements EncryptionInfoBuilder { - EncryptionInfo info; - BinaryRC4EncryptionHeader header; - BinaryRC4EncryptionVerifier verifier; - BinaryRC4Decryptor decryptor; - BinaryRC4Encryptor encryptor; - public BinaryRC4EncryptionInfoBuilder() { } + @Override public void initialize(EncryptionInfo info, LittleEndianInput dis) throws IOException { - this.info = info; int vMajor = info.getVersionMajor(); int vMinor = info.getVersionMinor(); assert (vMajor == 1 && vMinor == 1); - header = new BinaryRC4EncryptionHeader(); - verifier = new BinaryRC4EncryptionVerifier(dis); - decryptor = new BinaryRC4Decryptor(this); - encryptor = new BinaryRC4Encryptor(this); + info.setHeader(new BinaryRC4EncryptionHeader()); + info.setVerifier(new BinaryRC4EncryptionVerifier(dis)); + Decryptor dec = new BinaryRC4Decryptor(); + dec.setEncryptionInfo(info); + info.setDecryptor(dec); + Encryptor enc = new BinaryRC4Encryptor(); + enc.setEncryptionInfo(info); + info.setEncryptor(enc); } + @Override public void initialize(EncryptionInfo info, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) { - this.info = info; - header = new BinaryRC4EncryptionHeader(); - verifier = new BinaryRC4EncryptionVerifier(); - decryptor = new BinaryRC4Decryptor(this); - encryptor = new BinaryRC4Encryptor(this); - } - - public BinaryRC4EncryptionHeader getHeader() { - return header; - } - - public BinaryRC4EncryptionVerifier getVerifier() { - return verifier; - } - - public BinaryRC4Decryptor getDecryptor() { - return decryptor; - } - - public BinaryRC4Encryptor getEncryptor() { - return encryptor; - } - - public EncryptionInfo getEncryptionInfo() { - return info; + info.setHeader(new BinaryRC4EncryptionHeader()); + info.setVerifier(new BinaryRC4EncryptionVerifier()); + Decryptor dec = new BinaryRC4Decryptor(); + dec.setEncryptionInfo(info); + info.setDecryptor(dec); + Encryptor enc = new BinaryRC4Encryptor(); + enc.setEncryptionInfo(info); + info.setEncryptor(enc); } } diff --git a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionVerifier.java b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionVerifier.java index 86cf4ac18..654b64a3e 100644 --- a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionVerifier.java +++ b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionVerifier.java @@ -23,7 +23,7 @@ import org.apache.poi.poifs.crypt.standard.EncryptionRecord; import org.apache.poi.util.LittleEndianByteArrayOutputStream; import org.apache.poi.util.LittleEndianInput; -public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements EncryptionRecord { +public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements EncryptionRecord, Cloneable { protected BinaryRC4EncryptionVerifier() { setSpinCount(-1); @@ -50,6 +50,7 @@ public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements E setHashAlgorithm(HashAlgorithm.md5); } + @Override protected void setSalt(byte salt[]) { if (salt == null || salt.length != 16) { throw new EncryptedDocumentException("invalid verifier salt"); @@ -58,14 +59,17 @@ public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements E super.setSalt(salt); } + @Override protected void setEncryptedVerifier(byte encryptedVerifier[]) { super.setEncryptedVerifier(encryptedVerifier); } + @Override protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) { super.setEncryptedVerifierHash(encryptedVerifierHash); } + @Override public void write(LittleEndianByteArrayOutputStream bos) { byte salt[] = getSalt(); assert (salt.length == 16); @@ -78,4 +82,8 @@ public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements E bos.write(encryptedVerifierHash); } + @Override + public BinaryRC4EncryptionVerifier clone() throws CloneNotSupportedException { + return (BinaryRC4EncryptionVerifier)super.clone(); + } } diff --git a/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Encryptor.java b/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Encryptor.java index 2cf2d9334..ef49c9dc7 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 09f82e5a6..07b791074 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 000000000..573664932 --- /dev/null +++ b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentInputStream.java @@ -0,0 +1,86 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.poifs.crypt.cryptoapi; + +import java.io.ByteArrayInputStream; +import java.security.GeneralSecurityException; + +import javax.crypto.Cipher; +import javax.crypto.ShortBufferException; + +import org.apache.poi.EncryptedDocumentException; +import org.apache.poi.util.Internal; + +/** + * A seekable InputStream, which is used to decrypt/extract the document entries + * within the encrypted stream + */ +@Internal +/* package */ class CryptoAPIDocumentInputStream extends ByteArrayInputStream { + private Cipher cipher; + private final CryptoAPIDecryptor decryptor; + private byte oneByte[] = { 0 }; + + public void seek(int newpos) { + if (newpos > count) { + throw new ArrayIndexOutOfBoundsException(newpos); + } + + this.pos = newpos; + mark = newpos; + } + + public void setBlock(int block) throws GeneralSecurityException { + cipher = decryptor.initCipherForBlock(cipher, block); + } + + @Override + public synchronized int read() { + int ch = super.read(); + if (ch == -1) { + return -1; + } + oneByte[0] = (byte) ch; + try { + cipher.update(oneByte, 0, 1, oneByte); + } catch (ShortBufferException e) { + throw new EncryptedDocumentException(e); + } + return oneByte[0]; + } + + @Override + public synchronized int read(byte b[], int off, int len) { + int readLen = super.read(b, off, len); + if (readLen ==-1) { + return -1; + } + try { + cipher.update(b, off, readLen, b, off); + } catch (ShortBufferException e) { + throw new EncryptedDocumentException(e); + } + return readLen; + } + + public CryptoAPIDocumentInputStream(CryptoAPIDecryptor decryptor, byte buf[]) + throws GeneralSecurityException { + super(buf); + this.decryptor = decryptor; + cipher = decryptor.initCipherForBlock(null, 0); + } +} \ No newline at end of file diff --git a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentOutputStream.java b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentOutputStream.java new file mode 100644 index 000000000..6bf04871d --- /dev/null +++ b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentOutputStream.java @@ -0,0 +1,74 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.poifs.crypt.cryptoapi; + +import java.io.ByteArrayOutputStream; +import java.security.GeneralSecurityException; + +import javax.crypto.Cipher; + +import org.apache.poi.EncryptedDocumentException; +import org.apache.poi.util.Internal; + +/** + * An OutputStream for the document entries within the encrypted stream + */ +@Internal +/* package */ class CryptoAPIDocumentOutputStream extends ByteArrayOutputStream { + private Cipher cipher; + private CryptoAPIEncryptor encryptor; + private byte oneByte[] = { 0 }; + + public CryptoAPIDocumentOutputStream(CryptoAPIEncryptor encryptor) throws GeneralSecurityException { + this.encryptor = encryptor; + setBlock(0); + } + + public byte[] getBuf() { + return buf; + } + + public void setSize(int count) { + this.count = count; + } + + public void setBlock(int block) throws GeneralSecurityException { + cipher = encryptor.initCipherForBlock(cipher, block); + } + + @Override + public void write(int b) { + try { + oneByte[0] = (byte)b; + cipher.update(oneByte, 0, 1, oneByte, 0); + super.write(oneByte); + } catch (Exception e) { + throw new EncryptedDocumentException(e); + } + } + + @Override + public void write(byte[] b, int off, int len) { + try { + cipher.update(b, off, len, b, off); + super.write(b, off, len); + } catch (Exception e) { + throw new EncryptedDocumentException(e); + } + } + +} \ No newline at end of file diff --git a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionHeader.java b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionHeader.java index 151b6588a..b54dc2f0d 100644 --- a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionHeader.java +++ b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionHeader.java @@ -27,7 +27,7 @@ import org.apache.poi.poifs.crypt.HashAlgorithm; import org.apache.poi.poifs.crypt.standard.StandardEncryptionHeader; import org.apache.poi.util.LittleEndianInput; -public class CryptoAPIEncryptionHeader extends StandardEncryptionHeader { +public class CryptoAPIEncryptionHeader extends StandardEncryptionHeader implements Cloneable { public CryptoAPIEncryptionHeader(LittleEndianInput is) throws IOException { super(is); @@ -39,6 +39,7 @@ public class CryptoAPIEncryptionHeader extends StandardEncryptionHeader { super(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode); } + @Override public void setKeySize(int keyBits) { // Microsoft Base Cryptographic Provider is limited up to 40 bits // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375599(v=vs.85).aspx @@ -59,4 +60,9 @@ public class CryptoAPIEncryptionHeader extends StandardEncryptionHeader { setCspName(CipherProvider.rc4.cipherProviderName); } } + + @Override + public CryptoAPIEncryptionHeader clone() throws CloneNotSupportedException { + return (CryptoAPIEncryptionHeader)super.clone(); + } } diff --git a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionInfoBuilder.java b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionInfoBuilder.java index 36df52876..fef4dde16 100644 --- a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionInfoBuilder.java +++ b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionInfoBuilder.java @@ -23,63 +23,52 @@ import org.apache.poi.poifs.crypt.*; import org.apache.poi.util.LittleEndianInput; public class CryptoAPIEncryptionInfoBuilder implements EncryptionInfoBuilder { - EncryptionInfo info; - CryptoAPIEncryptionHeader header; - CryptoAPIEncryptionVerifier verifier; - CryptoAPIDecryptor decryptor; - CryptoAPIEncryptor encryptor; - public CryptoAPIEncryptionInfoBuilder() { } /** * initialize the builder from a stream */ + @Override public void initialize(EncryptionInfo info, LittleEndianInput dis) throws IOException { - this.info = info; /* int hSize = */ dis.readInt(); - header = new CryptoAPIEncryptionHeader(dis); - verifier = new CryptoAPIEncryptionVerifier(dis, header); - decryptor = new CryptoAPIDecryptor(this); - encryptor = new CryptoAPIEncryptor(this); + CryptoAPIEncryptionHeader header = new CryptoAPIEncryptionHeader(dis); + info.setHeader(header); + info.setVerifier(new CryptoAPIEncryptionVerifier(dis, header)); + CryptoAPIDecryptor dec = new CryptoAPIDecryptor(); + dec.setEncryptionInfo(info); + info.setDecryptor(dec); + CryptoAPIEncryptor enc = new CryptoAPIEncryptor(); + enc.setEncryptionInfo(info); + info.setEncryptor(enc); } /** * initialize the builder from scratch */ + @Override public void initialize(EncryptionInfo info, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) { - this.info = info; - if (cipherAlgorithm == null) cipherAlgorithm = CipherAlgorithm.rc4; - if (hashAlgorithm == null) hashAlgorithm = HashAlgorithm.sha1; - if (keyBits == -1) keyBits = 0x28; + if (cipherAlgorithm == null) { + cipherAlgorithm = CipherAlgorithm.rc4; + } + if (hashAlgorithm == null) { + hashAlgorithm = HashAlgorithm.sha1; + } + if (keyBits == -1) { + keyBits = 0x28; + } assert(cipherAlgorithm == CipherAlgorithm.rc4 && hashAlgorithm == HashAlgorithm.sha1); - header = new CryptoAPIEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode); - verifier = new CryptoAPIEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode); - decryptor = new CryptoAPIDecryptor(this); - encryptor = new CryptoAPIEncryptor(this); - } - - public CryptoAPIEncryptionHeader getHeader() { - return header; - } - - public CryptoAPIEncryptionVerifier getVerifier() { - return verifier; - } - - public CryptoAPIDecryptor getDecryptor() { - return decryptor; - } - - public CryptoAPIEncryptor getEncryptor() { - return encryptor; - } - - public EncryptionInfo getEncryptionInfo() { - return info; + info.setHeader(new CryptoAPIEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode)); + info.setVerifier(new CryptoAPIEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode)); + CryptoAPIDecryptor dec = new CryptoAPIDecryptor(); + dec.setEncryptionInfo(info); + info.setDecryptor(dec); + CryptoAPIEncryptor enc = new CryptoAPIEncryptor(); + enc.setEncryptionInfo(info); + info.setEncryptor(enc); } } diff --git a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionVerifier.java b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionVerifier.java index 160d1f9f9..d2c87b7ab 100644 --- a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionVerifier.java +++ b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionVerifier.java @@ -23,7 +23,7 @@ import org.apache.poi.poifs.crypt.HashAlgorithm; import org.apache.poi.poifs.crypt.standard.StandardEncryptionVerifier; import org.apache.poi.util.LittleEndianInput; -public class CryptoAPIEncryptionVerifier extends StandardEncryptionVerifier { +public class CryptoAPIEncryptionVerifier extends StandardEncryptionVerifier implements Cloneable { protected CryptoAPIEncryptionVerifier(LittleEndianInput is, CryptoAPIEncryptionHeader header) { @@ -36,15 +36,23 @@ public class CryptoAPIEncryptionVerifier extends StandardEncryptionVerifier { super(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode); } + @Override protected void setSalt(byte salt[]) { super.setSalt(salt); } + @Override protected void setEncryptedVerifier(byte encryptedVerifier[]) { super.setEncryptedVerifier(encryptedVerifier); } + @Override protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) { super.setEncryptedVerifierHash(encryptedVerifierHash); } + + @Override + public CryptoAPIEncryptionVerifier clone() throws CloneNotSupportedException { + return (CryptoAPIEncryptionVerifier)super.clone(); + } } diff --git a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptor.java b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptor.java index 47d4696cb..02d28761a 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 a6d6dbfa0..d06f9a373 100644 --- a/src/java/org/apache/poi/poifs/crypt/standard/StandardDecryptor.java +++ b/src/java/org/apache/poi/poifs/crypt/standard/StandardDecryptor.java @@ -34,7 +34,6 @@ import org.apache.poi.poifs.crypt.ChainingMode; import org.apache.poi.poifs.crypt.CryptoFunctions; import org.apache.poi.poifs.crypt.Decryptor; import org.apache.poi.poifs.crypt.EncryptionHeader; -import org.apache.poi.poifs.crypt.EncryptionInfoBuilder; import org.apache.poi.poifs.crypt.EncryptionVerifier; import org.apache.poi.poifs.crypt.HashAlgorithm; import org.apache.poi.poifs.filesystem.DirectoryNode; @@ -44,15 +43,15 @@ import org.apache.poi.util.LittleEndian; /** */ -public class StandardDecryptor extends Decryptor { +public class StandardDecryptor extends Decryptor implements Cloneable { private long _length = -1; - protected StandardDecryptor(EncryptionInfoBuilder builder) { - super(builder); + protected StandardDecryptor() { } + @Override public boolean verifyPassword(String password) { - EncryptionVerifier ver = builder.getVerifier(); + EncryptionVerifier ver = getEncryptionInfo().getVerifier(); SecretKey skey = generateSecretKey(password, ver, getKeySizeInBytes()); Cipher cipher = getCipher(skey); @@ -116,12 +115,13 @@ public class StandardDecryptor extends Decryptor { } private Cipher getCipher(SecretKey key) { - EncryptionHeader em = builder.getHeader(); + EncryptionHeader em = getEncryptionInfo().getHeader(); ChainingMode cm = em.getChainingMode(); assert(cm == ChainingMode.ecb); return CryptoFunctions.getCipher(key, em.getCipherAlgorithm(), cm, null, Cipher.DECRYPT_MODE); } + @Override @SuppressWarnings("resource") public InputStream getDataStream(DirectoryNode dir) throws IOException { DocumentInputStream dis = dir.createDocumentInputStream(DEFAULT_POIFS_ENTRY); @@ -134,7 +134,7 @@ public class StandardDecryptor extends Decryptor { // limit wrong calculated ole entries - (bug #57080) // standard encryption always uses aes encoding, so blockSize is always 16 // http://stackoverflow.com/questions/3283787/size-of-data-after-aes-encryption - int blockSize = builder.getHeader().getCipherAlgorithm().blockSize; + int blockSize = getEncryptionInfo().getHeader().getCipherAlgorithm().blockSize; long cipherLen = (_length/blockSize + 1) * blockSize; Cipher cipher = getCipher(getSecretKey()); @@ -145,8 +145,16 @@ public class StandardDecryptor extends Decryptor { /** * @return the length of the stream returned by {@link #getDataStream(DirectoryNode)} */ + @Override public long getLength(){ - if(_length == -1) throw new IllegalStateException("Decryptor.getDataStream() was not called"); + if(_length == -1) { + throw new IllegalStateException("Decryptor.getDataStream() was not called"); + } return _length; } + + @Override + public StandardDecryptor clone() throws CloneNotSupportedException { + return (StandardDecryptor)super.clone(); + } } diff --git a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionHeader.java b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionHeader.java index 44d7bc595..30f35581f 100644 --- a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionHeader.java +++ b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionHeader.java @@ -22,6 +22,7 @@ import static org.apache.poi.poifs.crypt.EncryptionInfo.flagCryptoAPI; import java.io.IOException; import java.io.InputStream; +import org.apache.poi.hssf.record.RecordInputStream; import org.apache.poi.poifs.crypt.ChainingMode; import org.apache.poi.poifs.crypt.CipherAlgorithm; import org.apache.poi.poifs.crypt.CipherProvider; @@ -33,7 +34,7 @@ import org.apache.poi.util.LittleEndianInput; import org.apache.poi.util.LittleEndianOutput; import org.apache.poi.util.StringUtil; -public class StandardEncryptionHeader extends EncryptionHeader implements EncryptionRecord { +public class StandardEncryptionHeader extends EncryptionHeader implements EncryptionRecord, Cloneable { protected StandardEncryptionHeader(LittleEndianInput is) throws IOException { setFlags(is.readInt()); @@ -55,9 +56,17 @@ public class StandardEncryptionHeader extends EncryptionHeader implements Encryp // CSPName may not always be specified // In some cases, the salt value of the EncryptionVerifier is the next chunk of data - ((InputStream)is).mark(LittleEndianConsts.INT_SIZE+1); + if (is instanceof RecordInputStream) { + ((RecordInputStream)is).mark(LittleEndianConsts.INT_SIZE+1); + } else { + ((InputStream)is).mark(LittleEndianConsts.INT_SIZE+1); + } int checkForSalt = is.readInt(); - ((InputStream)is).reset(); + if (is instanceof RecordInputStream) { + ((RecordInputStream)is).reset(); + } else { + ((InputStream)is).reset(); + } if (checkForSalt == 16) { setCspName(""); @@ -65,7 +74,9 @@ public class StandardEncryptionHeader extends EncryptionHeader implements Encryp StringBuilder builder = new StringBuilder(); while (true) { char c = (char) is.readShort(); - if (c == 0) break; + if (c == 0) { + break; + } builder.append(c); } setCspName(builder.toString()); @@ -90,6 +101,7 @@ public class StandardEncryptionHeader extends EncryptionHeader implements Encryp /** * serializes the header */ + @Override public void write(LittleEndianByteArrayOutputStream bos) { int startIdx = bos.getWriteIndex(); LittleEndianOutput sizeOutput = bos.createDelayedOutput(LittleEndianConsts.INT_SIZE); @@ -102,10 +114,17 @@ public class StandardEncryptionHeader extends EncryptionHeader implements Encryp bos.writeInt(0); // reserved1 bos.writeInt(0); // reserved2 String cspName = getCspName(); - if (cspName == null) cspName = getCipherProvider().cipherProviderName; + if (cspName == null) { + cspName = getCipherProvider().cipherProviderName; + } bos.write(StringUtil.getToUnicodeLE(cspName)); bos.writeShort(0); int headerSize = bos.getWriteIndex()-startIdx-LittleEndianConsts.INT_SIZE; sizeOutput.writeInt(headerSize); } + + @Override + public StandardEncryptionHeader clone() throws CloneNotSupportedException { + return (StandardEncryptionHeader)super.clone(); + } } diff --git a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionInfoBuilder.java b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionInfoBuilder.java index d55c8e0d6..c2bffdd05 100644 --- a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionInfoBuilder.java +++ b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionInfoBuilder.java @@ -27,34 +27,29 @@ import org.apache.poi.poifs.crypt.HashAlgorithm; import org.apache.poi.util.LittleEndianInput; public class StandardEncryptionInfoBuilder implements EncryptionInfoBuilder { - - EncryptionInfo info; - StandardEncryptionHeader header; - StandardEncryptionVerifier verifier; - StandardDecryptor decryptor; - StandardEncryptor encryptor; /** * initialize the builder from a stream */ + @Override public void initialize(EncryptionInfo info, LittleEndianInput dis) throws IOException { - this.info = info; - /* int hSize = */ dis.readInt(); - header = new StandardEncryptionHeader(dis); - verifier = new StandardEncryptionVerifier(dis, header); + StandardEncryptionHeader header = new StandardEncryptionHeader(dis); + info.setHeader(header); + info.setVerifier(new StandardEncryptionVerifier(dis, header)); if (info.getVersionMinor() == 2 && (info.getVersionMajor() == 3 || info.getVersionMajor() == 4)) { - decryptor = new StandardDecryptor(this); + StandardDecryptor dec = new StandardDecryptor(); + dec.setEncryptionInfo(info); + info.setDecryptor(dec); } } /** * initialize the builder from scratch */ + @Override public void initialize(EncryptionInfo info, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) { - this.info = info; - if (cipherAlgorithm == null) { cipherAlgorithm = CipherAlgorithm.aes128; } @@ -89,29 +84,13 @@ public class StandardEncryptionInfoBuilder implements EncryptionInfoBuilder { if (!found) { throw new EncryptedDocumentException("KeySize "+keyBits+" not allowed for Cipher "+cipherAlgorithm.toString()); } - header = new StandardEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode); - verifier = new StandardEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode); - decryptor = new StandardDecryptor(this); - encryptor = new StandardEncryptor(this); - } - - public StandardEncryptionHeader getHeader() { - return header; - } - - public StandardEncryptionVerifier getVerifier() { - return verifier; - } - - public StandardDecryptor getDecryptor() { - return decryptor; - } - - public StandardEncryptor getEncryptor() { - return encryptor; - } - - public EncryptionInfo getEncryptionInfo() { - return info; + info.setHeader(new StandardEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode)); + info.setVerifier(new StandardEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode)); + StandardDecryptor dec = new StandardDecryptor(); + dec.setEncryptionInfo(info); + info.setDecryptor(dec); + StandardEncryptor enc = new StandardEncryptor(); + enc.setEncryptionInfo(info); + info.setEncryptor(enc); } } diff --git a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionVerifier.java b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionVerifier.java index 021d82f9e..f00efecfb 100644 --- a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionVerifier.java +++ b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionVerifier.java @@ -27,7 +27,7 @@ import org.apache.poi.util.LittleEndianInput; /** * Used when checking if a key is valid for a document */ -public class StandardEncryptionVerifier extends EncryptionVerifier implements EncryptionRecord { +public class StandardEncryptionVerifier extends EncryptionVerifier implements EncryptionRecord, Cloneable { private static final int SPIN_COUNT = 50000; private final int verifierHashSize; @@ -68,6 +68,7 @@ public class StandardEncryptionVerifier extends EncryptionVerifier implements En } // make method visible for this package + @Override protected void setSalt(byte salt[]) { if (salt == null || salt.length != 16) { throw new EncryptedDocumentException("invalid verifier salt"); @@ -76,15 +77,18 @@ public class StandardEncryptionVerifier extends EncryptionVerifier implements En } // make method visible for this package + @Override protected void setEncryptedVerifier(byte encryptedVerifier[]) { super.setEncryptedVerifier(encryptedVerifier); } // make method visible for this package + @Override protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) { super.setEncryptedVerifierHash(encryptedVerifierHash); } + @Override public void write(LittleEndianByteArrayOutputStream bos) { // see [MS-OFFCRYPTO] - 2.3.4.9 byte salt[] = getSalt(); @@ -115,4 +119,9 @@ public class StandardEncryptionVerifier extends EncryptionVerifier implements En protected int getVerifierHashSize() { return verifierHashSize; } + + @Override + public StandardEncryptionVerifier clone() throws CloneNotSupportedException { + return (StandardEncryptionVerifier)super.clone(); + } } diff --git a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptor.java b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptor.java index fded6c606..5372c2af8 100644 --- a/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptor.java +++ b/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptor.java @@ -53,15 +53,13 @@ import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; import org.apache.poi.util.TempFile; -public class StandardEncryptor extends Encryptor { +public class StandardEncryptor extends Encryptor implements Cloneable { private static final POILogger logger = POILogFactory.getLogger(StandardEncryptor.class); - private final StandardEncryptionInfoBuilder builder; - - protected StandardEncryptor(StandardEncryptionInfoBuilder builder) { - this.builder = builder; + protected StandardEncryptor() { } + @Override public void confirmPassword(String password) { // see [MS-OFFCRYPTO] - 2.3.3 EncryptionVerifier Random r = new SecureRandom(); @@ -79,8 +77,9 @@ public class StandardEncryptor extends Encryptor { * * see [MS-OFFCRYPTO] - 2.3.4.7 ECMA-376 Document Encryption Key Generation */ + @Override public void confirmPassword(String password, byte keySpec[], byte keySalt[], byte verifier[], byte verifierSalt[], byte integritySalt[]) { - StandardEncryptionVerifier ver = builder.getVerifier(); + StandardEncryptionVerifier ver = (StandardEncryptionVerifier)getEncryptionInfo().getVerifier(); ver.setSalt(verifierSalt); SecretKey secretKey = generateSecretKey(password, ver, getKeySizeInBytes()); @@ -111,10 +110,11 @@ public class StandardEncryptor extends Encryptor { } private Cipher getCipher(SecretKey key, String padding) { - EncryptionVerifier ver = builder.getVerifier(); + EncryptionVerifier ver = getEncryptionInfo().getVerifier(); return CryptoFunctions.getCipher(key, ver.getCipherAlgorithm(), ver.getChainingMode(), null, Cipher.ENCRYPT_MODE, padding); } + @Override public OutputStream getDataStream(final DirectoryNode dir) throws IOException, GeneralSecurityException { createEncryptionInfoEntry(dir); @@ -163,6 +163,7 @@ public class StandardEncryptor extends Encryptor { countBytes++; } + @Override public void close() throws IOException { // the CipherOutputStream adds the padding bytes on close() super.close(); @@ -175,6 +176,7 @@ public class StandardEncryptor extends Encryptor { // TODO: any properties??? } + @Override public void processPOIFSWriterEvent(POIFSWriterEvent event) { try { LittleEndianOutputStream leos = new LittleEndianOutputStream(event.getStream()); @@ -200,15 +202,16 @@ public class StandardEncryptor extends Encryptor { } protected int getKeySizeInBytes() { - return builder.getHeader().getKeySize()/8; + return getEncryptionInfo().getHeader().getKeySize()/8; } protected void createEncryptionInfoEntry(DirectoryNode dir) throws IOException { - final EncryptionInfo info = builder.getEncryptionInfo(); - final StandardEncryptionHeader header = builder.getHeader(); - final StandardEncryptionVerifier verifier = builder.getVerifier(); + final EncryptionInfo info = getEncryptionInfo(); + final StandardEncryptionHeader header = (StandardEncryptionHeader)info.getHeader(); + final StandardEncryptionVerifier verifier = (StandardEncryptionVerifier)info.getVerifier(); EncryptionRecord er = new EncryptionRecord(){ + @Override public void write(LittleEndianByteArrayOutputStream bos) { bos.writeShort(info.getVersionMajor()); bos.writeShort(info.getVersionMinor()); @@ -222,4 +225,9 @@ public class StandardEncryptor extends Encryptor { // TODO: any properties??? } + + @Override + public StandardEncryptor clone() throws CloneNotSupportedException { + return (StandardEncryptor)super.clone(); + } } diff --git a/src/java/org/apache/poi/util/LittleEndianByteArrayInputStream.java b/src/java/org/apache/poi/util/LittleEndianByteArrayInputStream.java index 54388e151..4594343bf 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 081309cc2..fe9949f1a 100644 --- a/src/java/org/apache/poi/util/LittleEndianByteArrayOutputStream.java +++ b/src/java/org/apache/poi/util/LittleEndianByteArrayOutputStream.java @@ -17,28 +17,26 @@ package org.apache.poi.util; +import java.io.OutputStream; /** - * Adapts a plain byte array to {@link LittleEndianOutput} - * - * - * @author Josh Micich + * Adapts a plain byte array to {@link LittleEndianOutput} */ -public final class LittleEndianByteArrayOutputStream implements LittleEndianOutput, DelayableLittleEndianOutput { +public final class LittleEndianByteArrayOutputStream extends OutputStream implements LittleEndianOutput, DelayableLittleEndianOutput { private final byte[] _buf; private final int _endIndex; private int _writeIndex; public LittleEndianByteArrayOutputStream(byte[] buf, int startOffset, int maxWriteLen) { // NOSONAR if (startOffset < 0 || startOffset > buf.length) { - throw new IllegalArgumentException("Specified startOffset (" + startOffset + throw new IllegalArgumentException("Specified startOffset (" + startOffset + ") is out of allowable range (0.." + buf.length + ")"); } _buf = buf; _writeIndex = startOffset; _endIndex = startOffset + maxWriteLen; if (_endIndex < startOffset || _endIndex > buf.length) { - throw new IllegalArgumentException("calculated end index (" + _endIndex + throw new IllegalArgumentException("calculated end index (" + _endIndex + ") is out of allowable range (" + _writeIndex + ".." + buf.length + ")"); } } @@ -52,16 +50,19 @@ public final class LittleEndianByteArrayOutputStream implements LittleEndianOutp } } - public void writeByte(int v) { + @Override + public void writeByte(int v) { checkPosition(1); _buf[_writeIndex++] = (byte)v; } - public void writeDouble(double v) { + @Override + public void writeDouble(double v) { writeLong(Double.doubleToLongBits(v)); } - public void writeInt(int v) { + @Override + public void writeInt(int v) { checkPosition(4); int i = _writeIndex; _buf[i++] = (byte)((v >>> 0) & 0xFF); @@ -71,33 +72,47 @@ public final class LittleEndianByteArrayOutputStream implements LittleEndianOutp _writeIndex = i; } - public void writeLong(long v) { + @Override + public void writeLong(long v) { writeInt((int)(v >> 0)); writeInt((int)(v >> 32)); } - public void writeShort(int v) { + @Override + public void writeShort(int v) { checkPosition(2); int i = _writeIndex; _buf[i++] = (byte)((v >>> 0) & 0xFF); _buf[i++] = (byte)((v >>> 8) & 0xFF); _writeIndex = i; } - public void write(byte[] b) { + + @Override + public void write(int b) { + writeByte(b); + } + + @Override + public void write(byte[] b) { int len = b.length; checkPosition(len); System.arraycopy(b, 0, _buf, _writeIndex, len); _writeIndex += len; } - public void write(byte[] b, int offset, int len) { + + @Override + public void write(byte[] b, int offset, int len) { checkPosition(len); System.arraycopy(b, offset, _buf, _writeIndex, len); _writeIndex += len; } + public int getWriteIndex() { return _writeIndex; } - public LittleEndianOutput createDelayedOutput(int size) { + + @Override + public LittleEndianOutput createDelayedOutput(int size) { checkPosition(size); LittleEndianOutput result = new LittleEndianByteArrayOutputStream(_buf, _writeIndex, size); _writeIndex += size; diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java index f8c9bb551..480137ef8 100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java @@ -45,7 +45,7 @@ import org.apache.poi.poifs.crypt.CipherAlgorithm; import org.apache.poi.poifs.crypt.CryptoFunctions; import org.apache.poi.poifs.crypt.Decryptor; import org.apache.poi.poifs.crypt.EncryptionHeader; -import org.apache.poi.poifs.crypt.EncryptionInfoBuilder; +import org.apache.poi.poifs.crypt.EncryptionInfo; import org.apache.poi.poifs.crypt.EncryptionVerifier; import org.apache.poi.poifs.crypt.HashAlgorithm; import org.apache.poi.poifs.crypt.agile.AgileEncryptionVerifier.AgileCertificateEntry; @@ -56,14 +56,14 @@ import org.apache.poi.util.LittleEndian; /** * Decryptor implementation for Agile Encryption */ -public class AgileDecryptor extends Decryptor { +public class AgileDecryptor extends Decryptor implements Cloneable { private long _length = -1; - protected static final byte[] kVerifierInputBlock; - protected static final byte[] kHashedVerifierBlock; - protected static final byte[] kCryptoKeyBlock; - protected static final byte[] kIntegrityKeyBlock; - protected static final byte[] kIntegrityValueBlock; + /* package */ static final byte[] kVerifierInputBlock; + /* package */ static final byte[] kHashedVerifierBlock; + /* package */ static final byte[] kCryptoKeyBlock; + /* package */ static final byte[] kIntegrityKeyBlock; + /* package */ static final byte[] kIntegrityValueBlock; static { kVerifierInputBlock = @@ -83,16 +83,16 @@ public class AgileDecryptor extends Decryptor { (byte)0xb2, (byte)0x2c, (byte)0x84, (byte)0x33 }; } - protected AgileDecryptor(AgileEncryptionInfoBuilder builder) { - super(builder); + protected AgileDecryptor() { } /** * set decryption password */ + @Override public boolean verifyPassword(String password) throws GeneralSecurityException { - AgileEncryptionVerifier ver = (AgileEncryptionVerifier)builder.getVerifier(); - AgileEncryptionHeader header = (AgileEncryptionHeader)builder.getHeader(); + AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier(); + AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader(); HashAlgorithm hashAlgo = header.getHashAlgorithmEx(); CipherAlgorithm cipherAlgo = header.getCipherAlgorithm(); int blockSize = header.getBlockSize(); @@ -113,7 +113,7 @@ public class AgileDecryptor extends Decryptor { * blockSize bytes. * 4. Use base64 to encode the result of step 3. */ - byte verfierInputEnc[] = hashInput(builder, pwHash, kVerifierInputBlock, ver.getEncryptedVerifier(), Cipher.DECRYPT_MODE); + byte verfierInputEnc[] = hashInput(getEncryptionInfo(), pwHash, kVerifierInputBlock, ver.getEncryptedVerifier(), Cipher.DECRYPT_MODE); setVerifier(verfierInputEnc); MessageDigest hashMD = getMessageDigest(hashAlgo); byte[] verifierHash = hashMD.digest(verfierInputEnc); @@ -130,7 +130,7 @@ public class AgileDecryptor extends Decryptor { * blockSize bytes, pad the hash value with 0x00 to an integral multiple of blockSize bytes. * 4. Use base64 to encode the result of step 3. */ - byte verifierHashDec[] = hashInput(builder, pwHash, kHashedVerifierBlock, ver.getEncryptedVerifierHash(), Cipher.DECRYPT_MODE); + byte verifierHashDec[] = hashInput(getEncryptionInfo(), pwHash, kHashedVerifierBlock, ver.getEncryptedVerifierHash(), Cipher.DECRYPT_MODE); verifierHashDec = getBlock0(verifierHashDec, hashAlgo.hashSize); /** @@ -146,7 +146,7 @@ public class AgileDecryptor extends Decryptor { * blockSize bytes. * 4. Use base64 to encode the result of step 3. */ - byte keyspec[] = hashInput(builder, pwHash, kCryptoKeyBlock, ver.getEncryptedKey(), Cipher.DECRYPT_MODE); + byte keyspec[] = hashInput(getEncryptionInfo(), pwHash, kCryptoKeyBlock, ver.getEncryptedKey(), Cipher.DECRYPT_MODE); keyspec = getBlock0(keyspec, keySize); SecretKeySpec secretKey = new SecretKeySpec(keyspec, ver.getCipherAlgorithm().jceId); @@ -204,8 +204,8 @@ public class AgileDecryptor extends Decryptor { * @throws GeneralSecurityException */ public boolean verifyPassword(KeyPair keyPair, X509Certificate x509) throws GeneralSecurityException { - AgileEncryptionVerifier ver = (AgileEncryptionVerifier)builder.getVerifier(); - AgileEncryptionHeader header = (AgileEncryptionHeader)builder.getHeader(); + AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier(); + AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader(); HashAlgorithm hashAlgo = header.getHashAlgorithmEx(); CipherAlgorithm cipherAlgo = header.getCipherAlgorithm(); int blockSize = header.getBlockSize(); @@ -217,7 +217,9 @@ public class AgileDecryptor extends Decryptor { break; } } - if (ace == null) return false; + if (ace == null) { + return false; + } Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate()); @@ -255,9 +257,9 @@ public class AgileDecryptor extends Decryptor { return fillSize; } - protected static byte[] hashInput(EncryptionInfoBuilder builder, byte pwHash[], byte blockKey[], byte inputKey[], int cipherMode) { - EncryptionVerifier ver = builder.getVerifier(); - AgileDecryptor dec = (AgileDecryptor)builder.getDecryptor(); + protected static byte[] hashInput(EncryptionInfo encryptionInfo, byte pwHash[], byte blockKey[], byte inputKey[], int cipherMode) { + EncryptionVerifier ver = encryptionInfo.getVerifier(); + AgileDecryptor dec = (AgileDecryptor)encryptionInfo.getDecryptor(); int keySize = dec.getKeySizeInBytes(); int blockSize = dec.getBlockSizeInBytes(); HashAlgorithm hashAlgo = ver.getHashAlgorithm(); @@ -278,6 +280,7 @@ public class AgileDecryptor extends Decryptor { } } + @Override @SuppressWarnings("resource") public InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException { DocumentInputStream dis = dir.createDocumentInputStream(DEFAULT_POIFS_ENTRY); @@ -285,17 +288,20 @@ public class AgileDecryptor extends Decryptor { return new AgileCipherInputStream(dis, _length); } + @Override public long getLength(){ - if(_length == -1) throw new IllegalStateException("EcmaDecryptor.getDataStream() was not called"); + if(_length == -1) { + throw new IllegalStateException("EcmaDecryptor.getDataStream() was not called"); + } return _length; } - protected static Cipher initCipherForBlock(Cipher existing, int block, boolean lastChunk, EncryptionInfoBuilder builder, SecretKey skey, int encryptionMode) + protected static Cipher initCipherForBlock(Cipher existing, int block, boolean lastChunk, EncryptionInfo encryptionInfo, SecretKey skey, int encryptionMode) throws GeneralSecurityException { - EncryptionHeader header = builder.getHeader(); - if (existing == null || lastChunk) { - String padding = (lastChunk ? "PKCS5Padding" : "NoPadding"); + EncryptionHeader header = encryptionInfo.getHeader(); + String padding = (lastChunk ? "PKCS5Padding" : "NoPadding"); + if (existing == null || !existing.getAlgorithm().endsWith(padding)) { existing = getCipher(skey, header.getCipherAlgorithm(), header.getChainingMode(), header.getKeySalt(), encryptionMode, padding); } @@ -339,9 +345,15 @@ public class AgileDecryptor extends Decryptor { // TODO: calculate integrity hmac while reading the stream // for a post-validation of the data + @Override protected Cipher initCipherForBlock(Cipher cipher, int block) throws GeneralSecurityException { - return AgileDecryptor.initCipherForBlock(cipher, block, false, builder, getSecretKey(), Cipher.DECRYPT_MODE); + return AgileDecryptor.initCipherForBlock(cipher, block, false, getEncryptionInfo(), getSecretKey(), Cipher.DECRYPT_MODE); } } + + @Override + public AgileDecryptor clone() throws CloneNotSupportedException { + return (AgileDecryptor)super.clone(); + } } diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionHeader.java b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionHeader.java index a5fb14428..88bccabf6 100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionHeader.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionHeader.java @@ -27,7 +27,7 @@ import com.microsoft.schemas.office.x2006.encryption.CTKeyData; import com.microsoft.schemas.office.x2006.encryption.EncryptionDocument; import com.microsoft.schemas.office.x2006.encryption.STCipherChaining; -public class AgileEncryptionHeader extends EncryptionHeader { +public class AgileEncryptionHeader extends EncryptionHeader implements Cloneable { private byte encryptedHmacKey[], encryptedHmacValue[]; public AgileEncryptionHeader(String descriptor) { @@ -99,6 +99,7 @@ public class AgileEncryptionHeader extends EncryptionHeader { } // make method visible for this package + @Override protected void setKeySalt(byte salt[]) { if (salt == null || salt.length != getBlockSize()) { throw new EncryptedDocumentException("invalid verifier salt"); @@ -121,4 +122,13 @@ public class AgileEncryptionHeader extends EncryptionHeader { protected void setEncryptedHmacValue(byte[] encryptedHmacValue) { this.encryptedHmacValue = (encryptedHmacValue == null) ? null : encryptedHmacValue.clone(); } + + @Override + public AgileEncryptionHeader clone() throws CloneNotSupportedException { + AgileEncryptionHeader other = (AgileEncryptionHeader)super.clone(); + other.encryptedHmacKey = (encryptedHmacKey == null) ? null : encryptedHmacKey.clone(); + other.encryptedHmacValue = (encryptedHmacValue == null) ? null : encryptedHmacValue.clone(); + return other; + } + } diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionInfoBuilder.java b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionInfoBuilder.java index ecf7fe8e6..acfa01581 100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionInfoBuilder.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionInfoBuilder.java @@ -35,30 +35,24 @@ import com.microsoft.schemas.office.x2006.encryption.EncryptionDocument; public class AgileEncryptionInfoBuilder implements EncryptionInfoBuilder { - EncryptionInfo info; - AgileEncryptionHeader header; - AgileEncryptionVerifier verifier; - AgileDecryptor decryptor; - AgileEncryptor encryptor; - @Override - public void initialize(EncryptionInfo ei, LittleEndianInput dis) throws IOException { - this.info = ei; - + public void initialize(EncryptionInfo info, LittleEndianInput dis) throws IOException { EncryptionDocument ed = parseDescriptor((InputStream)dis); - header = new AgileEncryptionHeader(ed); - verifier = new AgileEncryptionVerifier(ed); - if (ei.getVersionMajor() == EncryptionMode.agile.versionMajor - && ei.getVersionMinor() == EncryptionMode.agile.versionMinor) { - decryptor = new AgileDecryptor(this); - encryptor = new AgileEncryptor(this); + info.setHeader(new AgileEncryptionHeader(ed)); + info.setVerifier(new AgileEncryptionVerifier(ed)); + if (info.getVersionMajor() == EncryptionMode.agile.versionMajor + && info.getVersionMinor() == EncryptionMode.agile.versionMinor) { + AgileDecryptor dec = new AgileDecryptor(); + dec.setEncryptionInfo(info); + info.setDecryptor(dec); + AgileEncryptor enc = new AgileEncryptor(); + enc.setEncryptionInfo(info); + info.setEncryptor(enc); } } @Override - public void initialize(EncryptionInfo ei, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) { - this.info = ei; - + public void initialize(EncryptionInfo info, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) { if (cipherAlgorithm == null) { cipherAlgorithm = CipherAlgorithm.aes128; } @@ -87,30 +81,14 @@ public class AgileEncryptionInfoBuilder implements EncryptionInfoBuilder { if (!found) { throw new EncryptedDocumentException("KeySize "+keyBits+" not allowed for Cipher "+cipherAlgorithm.toString()); } - header = new AgileEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode); - verifier = new AgileEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode); - decryptor = new AgileDecryptor(this); - encryptor = new AgileEncryptor(this); - } - - public AgileEncryptionHeader getHeader() { - return header; - } - - public AgileEncryptionVerifier getVerifier() { - return verifier; - } - - public AgileDecryptor getDecryptor() { - return decryptor; - } - - public AgileEncryptor getEncryptor() { - return encryptor; - } - - protected EncryptionInfo getInfo() { - return info; + info.setHeader(new AgileEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode)); + info.setVerifier(new AgileEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode)); + AgileDecryptor dec = new AgileDecryptor(); + dec.setEncryptionInfo(info); + info.setDecryptor(dec); + AgileEncryptor enc = new AgileEncryptor(); + enc.setEncryptionInfo(info); + info.setEncryptor(enc); } protected static EncryptionDocument parseDescriptor(String descriptor) { diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionVerifier.java b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionVerifier.java index e2910431a..53d4cd6ed 100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionVerifier.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionVerifier.java @@ -39,7 +39,7 @@ import com.microsoft.schemas.office.x2006.keyEncryptor.password.CTPasswordKeyEnc /** * Used when checking if a key is valid for a document */ -public class AgileEncryptionVerifier extends EncryptionVerifier { +public class AgileEncryptionVerifier extends EncryptionVerifier implements Cloneable { public static class AgileCertificateEntry { X509Certificate x509; @@ -87,8 +87,9 @@ public class AgileEncryptionVerifier extends EncryptionVerifier { setEncryptedVerifierHash(keyData.getEncryptedVerifierHashValue()); int saltSize = keyData.getSaltSize(); - if (saltSize != getSalt().length) + if (saltSize != getSalt().length) { throw new EncryptedDocumentException("Invalid salt size"); + } switch (keyData.getCipherChaining().intValue()) { case STCipherChaining.INT_CHAINING_MODE_CBC: @@ -101,7 +102,9 @@ public class AgileEncryptionVerifier extends EncryptionVerifier { throw new EncryptedDocumentException("Unsupported chaining mode - "+keyData.getCipherChaining().toString()); } - if (!encList.hasNext()) return; + if (!encList.hasNext()) { + return; + } try { CertificateFactory cf = CertificateFactory.getInstance("X.509"); @@ -125,6 +128,7 @@ public class AgileEncryptionVerifier extends EncryptionVerifier { setSpinCount(100000); // TODO: use parameter } + @Override protected void setSalt(byte salt[]) { if (salt == null || salt.length != getCipherAlgorithm().blockSize) { throw new EncryptedDocumentException("invalid verifier salt"); @@ -133,16 +137,19 @@ public class AgileEncryptionVerifier extends EncryptionVerifier { } // make method visible for this package + @Override protected void setEncryptedVerifier(byte encryptedVerifier[]) { super.setEncryptedVerifier(encryptedVerifier); } // make method visible for this package + @Override protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) { super.setEncryptedVerifierHash(encryptedVerifierHash); } // make method visible for this package + @Override protected void setEncryptedKey(byte[] encryptedKey) { super.setEncryptedKey(encryptedKey); } @@ -156,4 +163,12 @@ public class AgileEncryptionVerifier extends EncryptionVerifier { public List getCertificates() { return certList; } + + @Override + public AgileEncryptionVerifier clone() throws CloneNotSupportedException { + AgileEncryptionVerifier other = (AgileEncryptionVerifier)super.clone(); + // TODO: deep copy of certList + other.certList = new ArrayList(certList); + return other; + } } diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptor.java b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptor.java index c9601f1ba..5ff45ad81 100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptor.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptor.java @@ -60,6 +60,7 @@ import org.apache.poi.poifs.crypt.standard.EncryptionRecord; import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndianByteArrayOutputStream; +import org.apache.poi.util.LittleEndianConsts; import org.apache.xmlbeans.XmlOptions; import com.microsoft.schemas.office.x2006.encryption.CTDataIntegrity; @@ -74,21 +75,20 @@ import com.microsoft.schemas.office.x2006.encryption.STHashAlgorithm; import com.microsoft.schemas.office.x2006.keyEncryptor.certificate.CTCertificateKeyEncryptor; import com.microsoft.schemas.office.x2006.keyEncryptor.password.CTPasswordKeyEncryptor; -public class AgileEncryptor extends Encryptor { - private final AgileEncryptionInfoBuilder builder; +public class AgileEncryptor extends Encryptor implements Cloneable { private byte integritySalt[]; private byte pwHash[]; - protected AgileEncryptor(AgileEncryptionInfoBuilder builder) { - this.builder = builder; + protected AgileEncryptor() { } + @Override public void confirmPassword(String password) { // see [MS-OFFCRYPTO] - 2.3.3 EncryptionVerifier Random r = new SecureRandom(); - int blockSize = builder.getHeader().getBlockSize(); - int keySize = builder.getHeader().getKeySize()/8; - int hashSize = builder.getHeader().getHashAlgorithmEx().hashSize; + int blockSize = getEncryptionInfo().getHeader().getBlockSize(); + int keySize = getEncryptionInfo().getHeader().getKeySize()/8; + int hashSize = getEncryptionInfo().getHeader().getHashAlgorithmEx().hashSize; byte[] newVerifierSalt = new byte[blockSize] , newVerifier = new byte[blockSize] @@ -104,10 +104,11 @@ public class AgileEncryptor extends Encryptor { confirmPassword(password, newKeySpec, newKeySalt, newVerifierSalt, newVerifier, newIntegritySalt); } - public void confirmPassword(String password, byte keySpec[], byte keySalt[], byte verifier[], byte verifierSalt[], byte integritySalt[]) { - AgileEncryptionVerifier ver = builder.getVerifier(); + @Override + public void confirmPassword(String password, byte keySpec[], byte keySalt[], byte verifier[], byte verifierSalt[], byte integritySalt[]) { + AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier(); ver.setSalt(verifierSalt); - AgileEncryptionHeader header = builder.getHeader(); + AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader(); header.setKeySalt(keySalt); HashAlgorithm hashAlgo = ver.getHashAlgorithm(); @@ -128,7 +129,7 @@ public class AgileEncryptor extends Encryptor { * blockSize bytes. * 4. Use base64 to encode the result of step 3. */ - byte encryptedVerifier[] = hashInput(builder, pwHash, kVerifierInputBlock, verifier, Cipher.ENCRYPT_MODE); + byte encryptedVerifier[] = hashInput(getEncryptionInfo(), pwHash, kVerifierInputBlock, verifier, Cipher.ENCRYPT_MODE); ver.setEncryptedVerifier(encryptedVerifier); @@ -146,7 +147,7 @@ public class AgileEncryptor extends Encryptor { */ MessageDigest hashMD = getMessageDigest(hashAlgo); byte[] hashedVerifier = hashMD.digest(verifier); - byte encryptedVerifierHash[] = hashInput(builder, pwHash, kHashedVerifierBlock, hashedVerifier, Cipher.ENCRYPT_MODE); + byte encryptedVerifierHash[] = hashInput(getEncryptionInfo(), pwHash, kHashedVerifierBlock, hashedVerifier, Cipher.ENCRYPT_MODE); ver.setEncryptedVerifierHash(encryptedVerifierHash); /** @@ -162,7 +163,7 @@ public class AgileEncryptor extends Encryptor { * blockSize bytes. * 4. Use base64 to encode the result of step 3. */ - byte encryptedKey[] = hashInput(builder, pwHash, kCryptoKeyBlock, keySpec, Cipher.ENCRYPT_MODE); + byte encryptedKey[] = hashInput(getEncryptionInfo(), pwHash, kCryptoKeyBlock, keySpec, Cipher.ENCRYPT_MODE); ver.setEncryptedKey(encryptedKey); SecretKey secretKey = new SecretKeySpec(keySpec, ver.getCipherAlgorithm().jceId); @@ -214,6 +215,7 @@ public class AgileEncryptor extends Encryptor { } } + @Override public OutputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException { // TODO: initialize headers @@ -234,14 +236,14 @@ public class AgileEncryptor extends Encryptor { // as the integrity hmac needs to contain the StreamSize, // it's not possible to calculate it on-the-fly while buffering // TODO: add stream size parameter to getDataStream() - AgileEncryptionVerifier ver = builder.getVerifier(); + AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier(); HashAlgorithm hashAlgo = ver.getHashAlgorithm(); Mac integrityMD = CryptoFunctions.getMac(hashAlgo); integrityMD.init(new SecretKeySpec(integritySalt, hashAlgo.jceHmacId)); byte buf[] = new byte[1024]; LittleEndian.putLong(buf, 0, oleStreamSize); - integrityMD.update(buf, 0, LittleEndian.LONG_SIZE); + integrityMD.update(buf, 0, LittleEndianConsts.LONG_SIZE); InputStream fis = new FileInputStream(tmpFile); try { @@ -255,7 +257,7 @@ public class AgileEncryptor extends Encryptor { byte hmacValue[] = integrityMD.doFinal(); - AgileEncryptionHeader header = builder.getHeader(); + AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader(); int blockSize = header.getBlockSize(); byte iv[] = CryptoFunctions.generateIv(header.getHashAlgorithmEx(), header.getKeySalt(), kIntegrityValueBlock, blockSize); Cipher cipher = CryptoFunctions.getCipher(getSecretKey(), header.getCipherAlgorithm(), header.getChainingMode(), iv, Cipher.ENCRYPT_MODE); @@ -271,8 +273,8 @@ public class AgileEncryptor extends Encryptor { CTKeyEncryptor.Uri.HTTP_SCHEMAS_MICROSOFT_COM_OFFICE_2006_KEY_ENCRYPTOR_CERTIFICATE; protected EncryptionDocument createEncryptionDocument() { - AgileEncryptionVerifier ver = builder.getVerifier(); - AgileEncryptionHeader header = builder.getHeader(); + AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier(); + AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader(); EncryptionDocument ed = EncryptionDocument.Factory.newInstance(); CTEncryption edRoot = ed.addNewEncryption(); @@ -379,9 +381,10 @@ public class AgileEncryptor extends Encryptor { throws IOException, GeneralSecurityException { DataSpaceMapUtils.addDefaultDataSpace(dir); - final EncryptionInfo info = builder.getInfo(); + final EncryptionInfo info = getEncryptionInfo(); EncryptionRecord er = new EncryptionRecord(){ + @Override public void write(LittleEndianByteArrayOutputStream bos) { // EncryptionVersionInfo (4 bytes): A Version structure (section 2.1.4), where // Version.vMajor MUST be 0x0004 and Version.vMinor MUST be 0x0004 @@ -422,7 +425,7 @@ public class AgileEncryptor extends Encryptor { @Override protected Cipher initCipherForBlock(Cipher existing, int block, boolean lastChunk) throws GeneralSecurityException { - return AgileDecryptor.initCipherForBlock(existing, block, lastChunk, builder, getSecretKey(), Cipher.ENCRYPT_MODE); + return AgileDecryptor.initCipherForBlock(existing, block, lastChunk, getEncryptionInfo(), getSecretKey(), Cipher.ENCRYPT_MODE); } @Override @@ -439,4 +442,11 @@ public class AgileEncryptor extends Encryptor { } } + @Override + public AgileEncryptor clone() throws CloneNotSupportedException { + AgileEncryptor other = (AgileEncryptor)super.clone(); + other.integritySalt = (integritySalt == null) ? null : integritySalt.clone(); + other.pwHash = (pwHash == null) ? null : pwHash.clone(); + return other; + } } diff --git a/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestAgileEncryptionParameters.java b/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestAgileEncryptionParameters.java index 0d441d68d..2cefa95cf 100644 --- a/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestAgileEncryptionParameters.java +++ b/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestAgileEncryptionParameters.java @@ -16,8 +16,7 @@ ==================================================================== */ package org.apache.poi.poifs.crypt; -import static org.hamcrest.core.IsEqual.equalTo; -import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertTrue; import java.io.ByteArrayInputStream; @@ -94,6 +93,7 @@ public class TestAgileEncryptionParameters { os.close(); bos.reset(); fsEnc.writeFilesystem(bos); + fsEnc.close(); POIFSFileSystem fsDec = new POIFSFileSystem(new ByteArrayInputStream(bos.toByteArray())); EncryptionInfo infoDec = new EncryptionInfo(fsDec); @@ -103,6 +103,7 @@ public class TestAgileEncryptionParameters { InputStream is = dec.getDataStream(fsDec); byte actualData[] = IOUtils.toByteArray(is); is.close(); - assertThat("Failed roundtrip - "+ca+"-"+ha+"-"+cm, testData, equalTo(actualData)); + fsDec.close(); + assertArrayEquals("Failed roundtrip - "+ca+"-"+ha+"-"+cm, testData, actualData); } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowEncrypted.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowEncrypted.java index 3d77d92d2..ece68eef7 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowEncrypted.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowEncrypted.java @@ -17,6 +17,8 @@ package org.apache.poi.hslf.usermodel; +import java.io.Closeable; +import java.io.IOException; import java.io.OutputStream; import java.security.GeneralSecurityException; import java.util.ArrayList; @@ -25,9 +27,6 @@ import java.util.Map; import java.util.NavigableMap; import java.util.TreeMap; -import javax.crypto.Cipher; -import javax.crypto.CipherOutputStream; - import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException; import org.apache.poi.hslf.exceptions.EncryptedPowerPointFileException; import org.apache.poi.hslf.record.DocumentEncryptionAtom; @@ -36,26 +35,45 @@ import org.apache.poi.hslf.record.PositionDependentRecord; import org.apache.poi.hslf.record.Record; import org.apache.poi.hslf.record.UserEditAtom; import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey; +import org.apache.poi.poifs.crypt.ChunkedCipherInputStream; +import org.apache.poi.poifs.crypt.ChunkedCipherOutputStream; import org.apache.poi.poifs.crypt.Decryptor; import org.apache.poi.poifs.crypt.EncryptionInfo; import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIDecryptor; import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptor; import org.apache.poi.util.BitField; +import org.apache.poi.util.IOUtils; import org.apache.poi.util.Internal; import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.LittleEndianByteArrayInputStream; +import org.apache.poi.util.LittleEndianByteArrayOutputStream; /** * This class provides helper functions for encrypted PowerPoint documents. */ @Internal -public class HSLFSlideShowEncrypted { +public class HSLFSlideShowEncrypted implements Closeable { DocumentEncryptionAtom dea; CryptoAPIEncryptor enc = null; CryptoAPIDecryptor dec = null; - Cipher cipher = null; - CipherOutputStream cyos = null; +// Cipher cipher = null; + ChunkedCipherOutputStream cyos = null; private static final BitField fieldRecInst = new BitField(0xFFF0); + + private static final int BLIB_STORE_ENTRY_PARTS[] = { + 1, // btWin32 + 1, // btMacOS + 16, // rgbUid + 2, // tag + 4, // size + 4, // cRef + 4, // foDelay + 1, // unused1 + 1, // cbName (@ index 33) + 1, // unused2 + 1, // unused3 + }; protected HSLFSlideShowEncrypted(DocumentEncryptionAtom dea) { this.dea = dea; @@ -67,7 +85,9 @@ public class HSLFSlideShowEncrypted { UserEditAtom userEditAtomWithEncryption = null; for (Map.Entry me : recordMap.descendingMap().entrySet()) { Record r = me.getValue(); - if (!(r instanceof UserEditAtom)) continue; + if (!(r instanceof UserEditAtom)) { + continue; + } UserEditAtom uea = (UserEditAtom)r; if (uea.getEncryptSessionPersistIdRef() != -1) { userEditAtomWithEncryption = uea; @@ -83,7 +103,7 @@ public class HSLFSlideShowEncrypted { Record r = recordMap.get(userEditAtomWithEncryption.getPersistPointersOffset()); assert(r instanceof PersistPtrHolder); PersistPtrHolder ptr = (PersistPtrHolder)r; - + Integer encOffset = ptr.getSlideLocationsLookup().get(userEditAtomWithEncryption.getEncryptSessionPersistIdRef()); if (encOffset == null) { // encryption info doesn't exist anymore @@ -91,7 +111,7 @@ public class HSLFSlideShowEncrypted { dea = null; return; } - + r = recordMap.get(encOffset); if (r == null) { r = Record.buildRecordAtOffset(docstream, encOffset); @@ -100,7 +120,7 @@ public class HSLFSlideShowEncrypted { assert(r instanceof DocumentEncryptionAtom); this.dea = (DocumentEncryptionAtom)r; decryptInit(); - + String pass = Biff8EncryptionKey.getCurrentUserPassword(); if(!dec.verifyPassword(pass != null ? pass : Decryptor.DEFAULT_PASSWORD)) { throw new EncryptedPowerPointFileException("PowerPoint file is encrypted. The correct password needs to be set via Biff8EncryptionKey.setCurrentUserPassword()"); @@ -110,119 +130,144 @@ public class HSLFSlideShowEncrypted { public DocumentEncryptionAtom getDocumentEncryptionAtom() { return dea; } - - protected void setPersistId(int persistId) { - if (enc != null && dec != null) { - throw new EncryptedPowerPointFileException("Use instance either for en- or decryption"); - } - - try { - if (enc != null) cipher = enc.initCipherForBlock(cipher, persistId); - if (dec != null) cipher = dec.initCipherForBlock(cipher, persistId); - } catch (GeneralSecurityException e) { - throw new EncryptedPowerPointFileException(e); - } - } - + protected void decryptInit() { - if (dec != null) return; + if (dec != null) { + return; + } EncryptionInfo ei = dea.getEncryptionInfo(); dec = (CryptoAPIDecryptor)ei.getDecryptor(); } - + protected void encryptInit() { - if (enc != null) return; + if (enc != null) { + return; + } EncryptionInfo ei = dea.getEncryptionInfo(); enc = (CryptoAPIEncryptor)ei.getEncryptor(); } - - + + protected OutputStream encryptRecord(OutputStream plainStream, int persistId, Record record) { - boolean isPlain = (dea == null + boolean isPlain = (dea == null || record instanceof UserEditAtom || record instanceof PersistPtrHolder || record instanceof DocumentEncryptionAtom ); - if (isPlain) return plainStream; - encryptInit(); - setPersistId(persistId); - - if (cyos == null) { - cyos = new CipherOutputStream(plainStream, cipher); + try { + if (isPlain) { + if (cyos != null) { + // write cached data to stream + cyos.flush(); + } + return plainStream; + } + + encryptInit(); + + if (cyos == null) { + enc.setChunkSize(-1); + cyos = enc.getDataStream(plainStream); + } + cyos.initCipherForBlock(persistId, false); + } catch (Exception e) { + throw new EncryptedPowerPointFileException(e); } return cyos; } + private static void readFully(ChunkedCipherInputStream ccis, byte[] docstream, int offset, int len) throws IOException { + if (IOUtils.readFully(ccis, docstream, offset, len) == -1) { + throw new EncryptedPowerPointFileException("unexpected EOF"); + } + } + protected void decryptRecord(byte[] docstream, int persistId, int offset) { - if (dea == null) return; + if (dea == null) { + return; + } decryptInit(); - setPersistId(persistId); - + dec.setChunkSize(-1); + LittleEndianByteArrayInputStream lei = new LittleEndianByteArrayInputStream(docstream, offset); + ChunkedCipherInputStream ccis = null; try { + ccis = dec.getDataStream(lei, docstream.length-offset, 0); + ccis.initCipherForBlock(persistId); + // decrypt header and read length to be decrypted - cipher.update(docstream, offset, 8, docstream, offset); + readFully(ccis, docstream, offset, 8); // decrypt the rest of the record int rlen = (int)LittleEndian.getUInt(docstream, offset+4); - cipher.update(docstream, offset+8, rlen, docstream, offset+8); - } catch (GeneralSecurityException e) { - throw new CorruptPowerPointFileException(e); - } - } + readFully(ccis, docstream, offset+8, rlen); + + } catch (Exception e) { + throw new EncryptedPowerPointFileException(e); + } finally { + try { + if (ccis != null) { + ccis.close(); + } + lei.close(); + } catch (IOException e) { + throw new EncryptedPowerPointFileException(e); + } + } + } + + private void decryptPicBytes(byte[] pictstream, int offset, int len) + throws IOException, GeneralSecurityException { + // when reading the picture elements, each time a segment is read, the cipher needs + // to be reset (usually done when calling Cipher.doFinal) + LittleEndianByteArrayInputStream lei = new LittleEndianByteArrayInputStream(pictstream, offset); + ChunkedCipherInputStream ccis = dec.getDataStream(lei, len, 0); + readFully(ccis, pictstream, offset, len); + ccis.close(); + lei.close(); + } protected void decryptPicture(byte[] pictstream, int offset) { - if (dea == null) return; - + if (dea == null) { + return; + } + decryptInit(); - setPersistId(0); - + try { // decrypt header and read length to be decrypted - cipher.doFinal(pictstream, offset, 8, pictstream, offset); + decryptPicBytes(pictstream, offset, 8); int recInst = fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset)); int recType = LittleEndian.getUShort(pictstream, offset+2); int rlen = (int)LittleEndian.getUInt(pictstream, offset+4); offset += 8; - int endOffset = offset + rlen; + int endOffset = offset + rlen; if (recType == 0xF007) { // TOOD: get a real example file ... to actual test the FBSE entry // not sure where the foDelay block is - + // File BLIP Store Entry (FBSE) - cipher.doFinal(pictstream, offset, 1, pictstream, offset); // btWin32 - offset++; - cipher.doFinal(pictstream, offset, 1, pictstream, offset); // btMacOS - offset++; - cipher.doFinal(pictstream, offset, 16, pictstream, offset); // rgbUid - offset += 16; - cipher.doFinal(pictstream, offset, 2, pictstream, offset); // tag - offset += 2; - cipher.doFinal(pictstream, offset, 4, pictstream, offset); // size - offset += 4; - cipher.doFinal(pictstream, offset, 4, pictstream, offset); // cRef - offset += 4; - cipher.doFinal(pictstream, offset, 4, pictstream, offset); // foDelay - offset += 4; - cipher.doFinal(pictstream, offset+0, 1, pictstream, offset+0); // unused1 - cipher.doFinal(pictstream, offset+1, 1, pictstream, offset+1); // cbName - cipher.doFinal(pictstream, offset+2, 1, pictstream, offset+2); // unused2 - cipher.doFinal(pictstream, offset+3, 1, pictstream, offset+3); // unused3 - int cbName = LittleEndian.getUShort(pictstream, offset+1); - offset += 4; + for (int part : BLIB_STORE_ENTRY_PARTS) { + decryptPicBytes(pictstream, offset, part); + } + offset += 36; + + int cbName = LittleEndian.getUShort(pictstream, offset-3); if (cbName > 0) { - cipher.doFinal(pictstream, offset, cbName, pictstream, offset); // nameData + // read nameData + decryptPicBytes(pictstream, offset, cbName); offset += cbName; } + if (offset == endOffset) { return; // no embedded blip } // fall through, read embedded blip now // update header data - cipher.doFinal(pictstream, offset, 8, pictstream, offset); + decryptPicBytes(pictstream, offset, 8); recInst = fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset)); recType = LittleEndian.getUShort(pictstream, offset+2); // rlen = (int)LittleEndian.getUInt(pictstream, offset+4); @@ -231,70 +276,73 @@ public class HSLFSlideShowEncrypted { int rgbUidCnt = (recInst == 0x217 || recInst == 0x3D5 || recInst == 0x46B || recInst == 0x543 || recInst == 0x6E1 || recInst == 0x6E3 || recInst == 0x6E5 || recInst == 0x7A9) ? 2 : 1; - + + // rgbUid 1/2 for (int i=0; i 0) { - cipher.doFinal(pictstream, offset, cbName, pictstream, offset); // nameData + ccos.write(pictstream, offset, cbName); + ccos.flush(); offset += cbName; } + if (offset == endOffset) { return; // no embedded blip } @@ -303,32 +351,45 @@ public class HSLFSlideShowEncrypted { // update header data recInst = fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset)); recType = LittleEndian.getUShort(pictstream, offset+2); - // rlen = (int) LittleEndian.getUInt(pictstream, offset+4); - cipher.doFinal(pictstream, offset, 8, pictstream, offset); + ccos.write(pictstream, offset, 8); + ccos.flush(); offset += 8; } - + int rgbUidCnt = (recInst == 0x217 || recInst == 0x3D5 || recInst == 0x46B || recInst == 0x543 || recInst == 0x6E1 || recInst == 0x6E3 || recInst == 0x6E5 || recInst == 0x7A9) ? 2 : 1; - + for (int i=0; i slideLocations = new TreeMap(); @@ -386,7 +447,7 @@ public class HSLFSlideShowEncrypted { uea = (UserEditAtom)pdr; continue; } - + if (pdr instanceof PersistPtrHolder) { if (pph != null) { duplicatedCount++; @@ -394,16 +455,18 @@ public class HSLFSlideShowEncrypted { pph = (PersistPtrHolder)pdr; for (Map.Entry me : pph.getSlideLocationsLookup().entrySet()) { Integer oldOffset = slideLocations.put(me.getKey(), me.getValue()); - if (oldOffset != null) obsoleteOffsets.add(oldOffset); + if (oldOffset != null) { + obsoleteOffsets.add(oldOffset); + } } continue; } - + recordMap.put(pdr.getLastOnDiskOffset(), r); } - + assert(uea != null && pph != null && uea.getPersistPointersOffset() == pph.getLastOnDiskOffset()); - + recordMap.put(pph.getLastOnDiskOffset(), pph); recordMap.put(uea.getLastOnDiskOffset(), uea); @@ -416,15 +479,15 @@ public class HSLFSlideShowEncrypted { for (Map.Entry me : slideLocations.entrySet()) { pph.addSlideLookup(me.getKey(), me.getValue()); } - + for (Integer oldOffset : obsoleteOffsets) { recordMap.remove(oldOffset); } - + return recordMap.values().toArray(new Record[recordMap.size()]); } - - + + protected static Record[] removeEncryptionRecord(Record records[]) { int deaSlideId = -1; int deaOffset = -1; @@ -444,23 +507,27 @@ public class HSLFSlideShowEncrypted { } recordList.add(r); } - + assert(ptr != null); - if (deaSlideId == -1 && deaOffset == -1) return records; - + if (deaSlideId == -1 && deaOffset == -1) { + return records; + } + TreeMap tm = new TreeMap(ptr.getSlideLocationsLookup()); ptr.clear(); int maxSlideId = -1; for (Map.Entry me : tm.entrySet()) { - if (me.getKey() == deaSlideId || me.getValue() == deaOffset) continue; + if (me.getKey() == deaSlideId || me.getValue() == deaOffset) { + continue; + } ptr.addSlideLookup(me.getKey(), me.getValue()); maxSlideId = Math.max(me.getKey(), maxSlideId); } - + uea.setMaxPersistWritten(maxSlideId); records = recordList.toArray(new Record[recordList.size()]); - + return records; } @@ -470,9 +537,13 @@ public class HSLFSlideShowEncrypted { int ueaIdx = -1, ptrIdx = -1, deaIdx = -1, idx = -1; for (Record r : records) { idx++; - if (r instanceof UserEditAtom) ueaIdx = idx; - else if (r instanceof PersistPtrHolder) ptrIdx = idx; - else if (r instanceof DocumentEncryptionAtom) deaIdx = idx; + if (r instanceof UserEditAtom) { + ueaIdx = idx; + } else if (r instanceof PersistPtrHolder) { + ptrIdx = idx; + } else if (r instanceof DocumentEncryptionAtom) { + deaIdx = idx; + } } assert(ueaIdx != -1 && ptrIdx != -1 && ptrIdx < ueaIdx); if (deaIdx != -1) { @@ -488,13 +559,22 @@ public class HSLFSlideShowEncrypted { ptr.addSlideLookup(nextSlideId, ptr.getLastOnDiskOffset()-1); uea.setEncryptSessionPersistIdRef(nextSlideId); uea.setMaxPersistWritten(nextSlideId); - + Record newRecords[] = new Record[records.length+1]; - if (ptrIdx > 0) System.arraycopy(records, 0, newRecords, 0, ptrIdx); - if (ptrIdx < records.length-1) System.arraycopy(records, ptrIdx, newRecords, ptrIdx+1, records.length-ptrIdx); + if (ptrIdx > 0) { + System.arraycopy(records, 0, newRecords, 0, ptrIdx); + } + if (ptrIdx < records.length-1) { + System.arraycopy(records, ptrIdx, newRecords, ptrIdx+1, records.length-ptrIdx); + } newRecords[ptrIdx] = dea; return newRecords; } } + public void close() throws IOException { + if (cyos != null) { + cyos.close(); + } + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowImpl.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowImpl.java index 4e4c28d2b..9f4fada4d 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowImpl.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowImpl.java @@ -54,6 +54,7 @@ import org.apache.poi.poifs.filesystem.EntryUtils; import org.apache.poi.poifs.filesystem.NPOIFSFileSystem; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.sl.usermodel.PictureData.PictureType; +import org.apache.poi.util.IOUtils; import org.apache.poi.util.LittleEndian; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; @@ -205,14 +206,12 @@ public final class HSLFSlideShowImpl extends POIDocument implements Closeable { // Grab the document stream int len = docProps.getSize(); - _docstream = new byte[len]; - InputStream is = directory.createDocumentInputStream("PowerPoint Document"); - int readLen = is.read(_docstream); - is.close(); - - if (len != readLen) { - throw new IOException("Document input stream ended prematurely - expected "+len+" bytes - received "+readLen+" bytes"); - } + InputStream is = directory.createDocumentInputStream("PowerPoint Document"); + try { + _docstream = IOUtils.toByteArray(is, len); + } finally { + is.close(); + } } /** @@ -364,17 +363,10 @@ public final class HSLFSlideShowImpl extends POIDocument implements Closeable { HSLFSlideShowEncrypted decryptData = new HSLFSlideShowEncrypted(getDocumentEncryptionAtom()); DocumentEntry entry = (DocumentEntry)directory.getEntry("Pictures"); - int len = entry.getSize(); - byte[] pictstream = new byte[len]; - DocumentInputStream is = directory.createDocumentInputStream(entry); - int readLen = is.read(pictstream); + DocumentInputStream is = directory.createDocumentInputStream(entry); + byte[] pictstream = IOUtils.toByteArray(is, entry.getSize()); is.close(); - if (len != readLen) { - throw new IOException("Picture stream ended prematurely - expected "+len+" bytes - received "+readLen+" bytes"); - } - - int pos = 0; // An empty picture record (length 0) will take up 8 bytes while (pos <= (pictstream.length-8)) { @@ -512,7 +504,7 @@ public final class HSLFSlideShowImpl extends POIDocument implements Closeable { } HSLFSlideShowEncrypted encData = new HSLFSlideShowEncrypted(getDocumentEncryptionAtom()); - + for (Record record : _records) { assert(record instanceof PositionDependentRecord); // We've already figured out their new location, and @@ -533,6 +525,8 @@ public final class HSLFSlideShowImpl extends POIDocument implements Closeable { record.writeOut(encData.encryptRecord(os, persistId, record)); } } + + encData.close(); // Update and write out the Current User atom int oldLastUserEditAtomPos = (int)currentUser.getCurrentEditOffset(); @@ -733,7 +727,7 @@ public final class HSLFSlideShowImpl extends POIDocument implements Closeable { if (dea != null) { CryptoAPIEncryptor enc = (CryptoAPIEncryptor)dea.getEncryptionInfo().getEncryptor(); try { - enc.getDataStream(outFS.getRoot()); // ignore OutputStream + enc.getSummaryEntries(outFS.getRoot()); // ignore OutputStream } catch (IOException e) { throw e; } catch (GeneralSecurityException e) { diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/record/TestDocumentEncryption.java b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestDocumentEncryption.java index b7e6ea947..62db5cb85 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/record/TestDocumentEncryption.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestDocumentEncryption.java @@ -25,6 +25,7 @@ import static org.junit.Assert.fail; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.FileOutputStream; import java.security.MessageDigest; import java.util.List; @@ -44,6 +45,7 @@ import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey; import org.apache.poi.poifs.crypt.CryptoFunctions; import org.apache.poi.poifs.crypt.EncryptionInfo; import org.apache.poi.poifs.crypt.HashAlgorithm; +import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIDecryptor; import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptionHeader; import org.apache.poi.poifs.filesystem.NPOIFSFileSystem; import org.apache.poi.poifs.filesystem.POIFSFileSystem; @@ -176,7 +178,7 @@ public class TestDocumentEncryption { DocumentEncryptionAtom dea = hss.getDocumentEncryptionAtom(); - POIFSFileSystem fs2 = new POIFSFileSystem(dea.getEncryptionInfo().getDecryptor().getDataStream(fs)); + POIFSFileSystem fs2 = ((CryptoAPIDecryptor)dea.getEncryptionInfo().getDecryptor()).getSummaryEntries(fs.getRoot(), "EncryptedSummary"); PropertySet ps = PropertySetFactory.create(fs2.getRoot(), SummaryInformation.DEFAULT_STREAM_NAME); assertTrue(ps.isSummaryInformation()); assertEquals("RC4 CryptoAPI Encryption", ps.getProperties()[1].getValue());