mirror of https://github.com/apache/poi.git
bug 60153: encrypt SXSSF temporary files; patch from PJ Fanning
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1763943 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
af4678d45e
commit
75ce299646
|
@ -24,8 +24,7 @@ import java.io.FileInputStream;
|
|||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.io.OutputStream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
|
@ -56,22 +55,14 @@ public class GZIPSheetDataWriter extends SheetDataWriter {
|
|||
return TempFile.createTempFile("poi-sxssf-sheet-xml", ".gz");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a wrapped instance of GZIPOutputStream
|
||||
*/
|
||||
@Override
|
||||
public Writer createWriter(File fd)throws IOException {
|
||||
return new OutputStreamWriter(new GZIPOutputStream(new FileOutputStream(fd)), "UTF-8");
|
||||
protected InputStream decorateInputStream(FileInputStream fis) throws IOException {
|
||||
return new GZIPInputStream(fis);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return a GZIPInputStream stream to read the compressed temp file
|
||||
*/
|
||||
@Override
|
||||
public InputStream getWorksheetXMLInputStream() throws IOException {
|
||||
File fd = getTempFile();
|
||||
return new GZIPInputStream(new FileInputStream(fd));
|
||||
protected OutputStream decorateOutputStream(FileOutputStream fos) throws IOException {
|
||||
return new GZIPOutputStream(fos);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -35,6 +35,8 @@ import java.util.zip.ZipFile;
|
|||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.apache.poi.openxml4j.opc.OPCPackage;
|
||||
import org.apache.poi.openxml4j.util.ZipEntrySource;
|
||||
import org.apache.poi.openxml4j.util.ZipFileZipEntrySource;
|
||||
import org.apache.poi.ss.SpreadsheetVersion;
|
||||
import org.apache.poi.ss.formula.udf.UDFFinder;
|
||||
import org.apache.poi.ss.usermodel.CellStyle;
|
||||
|
@ -46,6 +48,7 @@ import org.apache.poi.ss.usermodel.PictureData;
|
|||
import org.apache.poi.ss.usermodel.Row.MissingCellPolicy;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.util.Internal;
|
||||
import org.apache.poi.util.NotImplemented;
|
||||
import org.apache.poi.util.POILogFactory;
|
||||
import org.apache.poi.util.POILogger;
|
||||
|
@ -287,6 +290,14 @@ public class SXSSFWorkbook implements Workbook {
|
|||
_randomAccessWindowSize = rowAccessWindowSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether temp files should be compressed.
|
||||
*
|
||||
* @return whether to compress temp files
|
||||
*/
|
||||
public boolean isCompressTempFiles() {
|
||||
return _compressTmpFiles;
|
||||
}
|
||||
/**
|
||||
* Set whether temp files should be compressed.
|
||||
* <p>
|
||||
|
@ -300,11 +311,16 @@ public class SXSSFWorkbook implements Workbook {
|
|||
* </p>
|
||||
* @param compress whether to compress temp files
|
||||
*/
|
||||
public void setCompressTempFiles(boolean compress){
|
||||
public void setCompressTempFiles(boolean compress) {
|
||||
_compressTmpFiles = compress;
|
||||
}
|
||||
|
||||
@Internal
|
||||
protected SharedStringsTable getSharedStringSource() {
|
||||
return _sharedStringSource;
|
||||
}
|
||||
|
||||
SheetDataWriter createSheetDataWriter() throws IOException {
|
||||
protected SheetDataWriter createSheetDataWriter() throws IOException {
|
||||
if(_compressTmpFiles) {
|
||||
return new GZIPSheetDataWriter(_sharedStringSource);
|
||||
}
|
||||
|
@ -353,21 +369,19 @@ public class SXSSFWorkbook implements Workbook {
|
|||
return null;
|
||||
}
|
||||
|
||||
private void injectData(File zipfile, OutputStream out) throws IOException
|
||||
protected void injectData(ZipEntrySource zipEntrySource, OutputStream out) throws IOException
|
||||
{
|
||||
// don't use ZipHelper.openZipFile here - see #59743
|
||||
ZipFile zip = new ZipFile(zipfile);
|
||||
try
|
||||
{
|
||||
ZipOutputStream zos = new ZipOutputStream(out);
|
||||
try
|
||||
{
|
||||
Enumeration<? extends ZipEntry> en = zip.entries();
|
||||
Enumeration<? extends ZipEntry> en = zipEntrySource.getEntries();
|
||||
while (en.hasMoreElements())
|
||||
{
|
||||
ZipEntry ze = en.nextElement();
|
||||
zos.putNextEntry(new ZipEntry(ze.getName()));
|
||||
InputStream is = zip.getInputStream(ze);
|
||||
InputStream is = zipEntrySource.getInputStream(ze);
|
||||
XSSFSheet xSheet=getSheetFromZipEntryName(ze.getName());
|
||||
if(xSheet!=null)
|
||||
{
|
||||
|
@ -396,7 +410,7 @@ public class SXSSFWorkbook implements Workbook {
|
|||
}
|
||||
finally
|
||||
{
|
||||
zip.close();
|
||||
zipEntrySource.close();
|
||||
}
|
||||
}
|
||||
private static void copyStream(InputStream in, OutputStream out) throws IOException {
|
||||
|
@ -945,7 +959,8 @@ public class SXSSFWorkbook implements Workbook {
|
|||
}
|
||||
|
||||
//Substitute the template entries with the generated sheet data files
|
||||
injectData(tmplFile, stream);
|
||||
final ZipEntrySource source = new ZipFileZipEntrySource(new ZipFile(tmplFile));
|
||||
injectData(source, stream);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
@ -25,6 +25,7 @@ import java.io.FileInputStream;
|
|||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.util.Iterator;
|
||||
|
@ -69,7 +70,7 @@ public class SheetDataWriter {
|
|||
_out = createWriter(_fd);
|
||||
}
|
||||
|
||||
public SheetDataWriter(SharedStringsTable sharedStringsTable) throws IOException{
|
||||
public SheetDataWriter(SharedStringsTable sharedStringsTable) throws IOException {
|
||||
this();
|
||||
this._sharedStringSource = sharedStringsTable;
|
||||
}
|
||||
|
@ -90,8 +91,23 @@ public class SheetDataWriter {
|
|||
*
|
||||
* @param fd the file to write to
|
||||
*/
|
||||
public Writer createWriter(File fd)throws IOException {
|
||||
return new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fd), "UTF-8"));
|
||||
public Writer createWriter(File fd) throws IOException {
|
||||
final OutputStream decorated = decorateOutputStream(new FileOutputStream(fd));
|
||||
return new BufferedWriter(new OutputStreamWriter(decorated, "UTF-8"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this to translate (such as encrypt or compress) the file output stream
|
||||
* as it is being written to disk.
|
||||
* The default behavior is to to pass the stream through unmodified.
|
||||
*
|
||||
* @param fos the stream to decorate
|
||||
* @return a decorated stream
|
||||
* @throws IOException
|
||||
* @see #decorateInputStream(FileInputStream)
|
||||
*/
|
||||
protected OutputStream decorateOutputStream(FileOutputStream fos) throws IOException {
|
||||
return fos;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -112,7 +128,21 @@ public class SheetDataWriter {
|
|||
*/
|
||||
public InputStream getWorksheetXMLInputStream() throws IOException {
|
||||
File fd = getTempFile();
|
||||
return new FileInputStream(fd);
|
||||
return decorateInputStream(new FileInputStream(fd));
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this to translate (such as decrypt or expand) the file input stream
|
||||
* as it is being read from disk.
|
||||
* The default behavior is to to pass the stream through unmodified.
|
||||
*
|
||||
* @param fis the stream to decorate
|
||||
* @return a decorated stream
|
||||
* @throws IOException
|
||||
* @see #decorateOutputStream(FileOutputStream)
|
||||
*/
|
||||
protected InputStream decorateInputStream(FileInputStream fis) throws IOException {
|
||||
return fis;
|
||||
}
|
||||
|
||||
public int getNumberOfFlushedRows() {
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
/* ====================================================================
|
||||
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;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Enumeration;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipException;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
import javax.crypto.CipherOutputStream;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.apache.poi.openxml4j.util.ZipEntrySource;
|
||||
import org.apache.poi.util.IOUtils;
|
||||
import org.apache.poi.util.TempFile;
|
||||
|
||||
public class AesZipFileZipEntrySource implements ZipEntrySource {
|
||||
final File tmpFile;
|
||||
final ZipFile zipFile;
|
||||
final Cipher ci;
|
||||
boolean closed;
|
||||
|
||||
public AesZipFileZipEntrySource(File tmpFile, Cipher ci) throws IOException {
|
||||
this.tmpFile = tmpFile;
|
||||
this.zipFile = new ZipFile(tmpFile);
|
||||
this.ci = ci;
|
||||
this.closed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: the file sizes are rounded up to the next cipher block size,
|
||||
* so don't rely on file sizes of these custom encrypted zip file entries!
|
||||
*/
|
||||
public Enumeration<? extends ZipEntry> getEntries() {
|
||||
return zipFile.entries();
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
public InputStream getInputStream(ZipEntry entry) throws IOException {
|
||||
InputStream is = zipFile.getInputStream(entry);
|
||||
return new CipherInputStream(is, ci);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
zipFile.close();
|
||||
tmpFile.delete();
|
||||
closed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return closed;
|
||||
}
|
||||
|
||||
public static ZipEntrySource createZipEntrySource(InputStream is) throws IOException, GeneralSecurityException {
|
||||
// generate session key
|
||||
SecureRandom sr = new SecureRandom();
|
||||
byte[] ivBytes = new byte[16], keyBytes = new byte[16];
|
||||
sr.nextBytes(ivBytes);
|
||||
sr.nextBytes(keyBytes);
|
||||
final File tmpFile = TempFile.createTempFile("protectedXlsx", ".zip");
|
||||
copyToFile(is, tmpFile, CipherAlgorithm.aes128, keyBytes, ivBytes);
|
||||
IOUtils.closeQuietly(is);
|
||||
return fileToSource(tmpFile, CipherAlgorithm.aes128, keyBytes, ivBytes);
|
||||
}
|
||||
|
||||
private static void copyToFile(InputStream is, File tmpFile, CipherAlgorithm cipherAlgorithm, byte keyBytes[], byte ivBytes[]) throws IOException, GeneralSecurityException {
|
||||
SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, cipherAlgorithm.jceId);
|
||||
Cipher ciEnc = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.ENCRYPT_MODE, "PKCS5Padding");
|
||||
|
||||
ZipInputStream zis = new ZipInputStream(is);
|
||||
FileOutputStream fos = new FileOutputStream(tmpFile);
|
||||
ZipOutputStream zos = new ZipOutputStream(fos);
|
||||
|
||||
ZipEntry ze;
|
||||
while ((ze = zis.getNextEntry()) != null) {
|
||||
// the cipher output stream pads the data, therefore we can't reuse the ZipEntry with set sizes
|
||||
// as those will be validated upon close()
|
||||
ZipEntry zeNew = new ZipEntry(ze.getName());
|
||||
zeNew.setComment(ze.getComment());
|
||||
zeNew.setExtra(ze.getExtra());
|
||||
zeNew.setTime(ze.getTime());
|
||||
// zeNew.setMethod(ze.getMethod());
|
||||
zos.putNextEntry(zeNew);
|
||||
FilterOutputStream fos2 = new FilterOutputStream(zos){
|
||||
// don't close underlying ZipOutputStream
|
||||
public void close() {}
|
||||
};
|
||||
CipherOutputStream cos = new CipherOutputStream(fos2, ciEnc);
|
||||
IOUtils.copy(zis, cos);
|
||||
cos.close();
|
||||
fos2.close();
|
||||
zos.closeEntry();
|
||||
zis.closeEntry();
|
||||
}
|
||||
zos.close();
|
||||
fos.close();
|
||||
zis.close();
|
||||
}
|
||||
|
||||
private static ZipEntrySource fileToSource(File tmpFile, CipherAlgorithm cipherAlgorithm, byte keyBytes[], byte ivBytes[]) throws ZipException, IOException {
|
||||
SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, cipherAlgorithm.jceId);
|
||||
Cipher ciDec = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.DECRYPT_MODE, "PKCS5Padding");
|
||||
return new AesZipFileZipEntrySource(tmpFile, ciDec);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -22,30 +22,14 @@ import static org.junit.Assert.assertTrue;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Enumeration;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipException;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
import javax.crypto.CipherOutputStream;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
|
||||
import org.apache.poi.openxml4j.opc.OPCPackage;
|
||||
import org.apache.poi.openxml4j.util.ZipEntrySource;
|
||||
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
||||
import org.apache.poi.util.IOUtils;
|
||||
import org.apache.poi.util.TempFile;
|
||||
import org.apache.poi.xssf.XSSFTestDataSamples;
|
||||
import org.apache.poi.xssf.extractor.XSSFEventBasedExcelExtractor;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
|
@ -59,7 +43,6 @@ public class TestSecureTempZip {
|
|||
*/
|
||||
@Test
|
||||
public void protectedTempZip() throws IOException, GeneralSecurityException, XmlException, OpenXML4JException {
|
||||
final File tmpFile = TempFile.createTempFile("protectedXlsx", ".zip");
|
||||
File tikaProt = XSSFTestDataSamples.getSampleFile("protected_passtika.xlsx");
|
||||
FileInputStream fis = new FileInputStream(tikaProt);
|
||||
POIFSFileSystem poifs = new POIFSFileSystem(fis);
|
||||
|
@ -68,19 +51,11 @@ public class TestSecureTempZip {
|
|||
boolean passOk = dec.verifyPassword("tika");
|
||||
assertTrue(passOk);
|
||||
|
||||
// generate session key
|
||||
SecureRandom sr = new SecureRandom();
|
||||
byte[] ivBytes = new byte[16], keyBytes = new byte[16];
|
||||
sr.nextBytes(ivBytes);
|
||||
sr.nextBytes(keyBytes);
|
||||
|
||||
// extract encrypted ooxml file and write to custom encrypted zip file
|
||||
InputStream is = dec.getDataStream(poifs);
|
||||
copyToFile(is, tmpFile, CipherAlgorithm.aes128, keyBytes, ivBytes);
|
||||
is.close();
|
||||
|
||||
// provide ZipEntrySource to poi which decrypts on the fly
|
||||
ZipEntrySource source = fileToSource(tmpFile, CipherAlgorithm.aes128, keyBytes, ivBytes);
|
||||
ZipEntrySource source = AesZipFileZipEntrySource.createZipEntrySource(is);
|
||||
|
||||
// test the source
|
||||
OPCPackage opc = OPCPackage.open(source);
|
||||
|
@ -102,84 +77,5 @@ public class TestSecureTempZip {
|
|||
source.close();
|
||||
poifs.close();
|
||||
fis.close();
|
||||
tmpFile.delete();
|
||||
}
|
||||
|
||||
private void copyToFile(InputStream is, File tmpFile, CipherAlgorithm cipherAlgorithm, byte keyBytes[], byte ivBytes[]) throws IOException, GeneralSecurityException {
|
||||
SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, cipherAlgorithm.jceId);
|
||||
Cipher ciEnc = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.ENCRYPT_MODE, "PKCS5Padding");
|
||||
|
||||
ZipInputStream zis = new ZipInputStream(is);
|
||||
FileOutputStream fos = new FileOutputStream(tmpFile);
|
||||
ZipOutputStream zos = new ZipOutputStream(fos);
|
||||
|
||||
ZipEntry ze;
|
||||
while ((ze = zis.getNextEntry()) != null) {
|
||||
// the cipher output stream pads the data, therefore we can't reuse the ZipEntry with set sizes
|
||||
// as those will be validated upon close()
|
||||
ZipEntry zeNew = new ZipEntry(ze.getName());
|
||||
zeNew.setComment(ze.getComment());
|
||||
zeNew.setExtra(ze.getExtra());
|
||||
zeNew.setTime(ze.getTime());
|
||||
// zeNew.setMethod(ze.getMethod());
|
||||
zos.putNextEntry(zeNew);
|
||||
FilterOutputStream fos2 = new FilterOutputStream(zos){
|
||||
// don't close underlying ZipOutputStream
|
||||
public void close() {}
|
||||
};
|
||||
CipherOutputStream cos = new CipherOutputStream(fos2, ciEnc);
|
||||
IOUtils.copy(zis, cos);
|
||||
cos.close();
|
||||
fos2.close();
|
||||
zos.closeEntry();
|
||||
zis.closeEntry();
|
||||
}
|
||||
zos.close();
|
||||
fos.close();
|
||||
zis.close();
|
||||
}
|
||||
|
||||
private ZipEntrySource fileToSource(File tmpFile, CipherAlgorithm cipherAlgorithm, byte keyBytes[], byte ivBytes[]) throws ZipException, IOException {
|
||||
SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, cipherAlgorithm.jceId);
|
||||
Cipher ciDec = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.DECRYPT_MODE, "PKCS5Padding");
|
||||
ZipFile zf = new ZipFile(tmpFile);
|
||||
return new AesZipFileZipEntrySource(zf, ciDec);
|
||||
}
|
||||
|
||||
static class AesZipFileZipEntrySource implements ZipEntrySource {
|
||||
final ZipFile zipFile;
|
||||
final Cipher ci;
|
||||
boolean closed;
|
||||
|
||||
AesZipFileZipEntrySource(ZipFile zipFile, Cipher ci) {
|
||||
this.zipFile = zipFile;
|
||||
this.ci = ci;
|
||||
this.closed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: the file sizes are rounded up to the next cipher block size,
|
||||
* so don't rely on file sizes of these custom encrypted zip file entries!
|
||||
*/
|
||||
public Enumeration<? extends ZipEntry> getEntries() {
|
||||
return zipFile.entries();
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
public InputStream getInputStream(ZipEntry entry) throws IOException {
|
||||
InputStream is = zipFile.getInputStream(entry);
|
||||
return new CipherInputStream(is, ci);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
zipFile.close();
|
||||
closed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return closed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.xssf.streaming;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
import javax.crypto.CipherOutputStream;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.apache.poi.openxml4j.util.ZipEntrySource;
|
||||
import org.apache.poi.poifs.crypt.AesZipFileZipEntrySource;
|
||||
import org.apache.poi.poifs.crypt.ChainingMode;
|
||||
import org.apache.poi.poifs.crypt.CipherAlgorithm;
|
||||
import org.apache.poi.poifs.crypt.CryptoFunctions;
|
||||
import org.apache.poi.util.POILogFactory;
|
||||
import org.apache.poi.util.POILogger;
|
||||
import org.apache.poi.xssf.usermodel.XSSFCell;
|
||||
import org.apache.poi.xssf.usermodel.XSSFRow;
|
||||
import org.apache.poi.xssf.usermodel.XSSFSheet;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* This class tests that an SXSSFWorkbook can be written and read where all temporary disk I/O
|
||||
* is encrypted, but the final saved workbook is not encrypted
|
||||
*/
|
||||
public final class TestSXSSFWorkbookWithCustomZipEntrySource {
|
||||
|
||||
@Test
|
||||
public void customZipEntrySource() throws IOException, GeneralSecurityException {
|
||||
final String sheetName = "TestSheet1";
|
||||
final String cellValue = "customZipEntrySource";
|
||||
SXSSFWorkbookWithCustomZipEntrySource workbook = new SXSSFWorkbookWithCustomZipEntrySource();
|
||||
SXSSFSheet sheet1 = workbook.createSheet(sheetName);
|
||||
SXSSFRow row1 = sheet1.createRow(1);
|
||||
SXSSFCell cell1 = row1.createCell(1);
|
||||
cell1.setCellValue(cellValue);
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(8192);
|
||||
workbook.write(os);
|
||||
workbook.close();
|
||||
workbook.dispose();
|
||||
XSSFWorkbook xwb = new XSSFWorkbook(new ByteArrayInputStream(os.toByteArray()));
|
||||
XSSFSheet xs1 = xwb.getSheetAt(0);
|
||||
assertEquals(sheetName, xs1.getSheetName());
|
||||
XSSFRow xr1 = xs1.getRow(1);
|
||||
XSSFCell xc1 = xr1.getCell(1);
|
||||
assertEquals(cellValue, xc1.getStringCellValue());
|
||||
xwb.close();
|
||||
}
|
||||
|
||||
static class SXSSFWorkbookWithCustomZipEntrySource extends SXSSFWorkbook {
|
||||
|
||||
private static final POILogger logger = POILogFactory.getLogger(SXSSFWorkbookWithCustomZipEntrySource.class);
|
||||
|
||||
@Override
|
||||
public void write(OutputStream stream) throws IOException {
|
||||
flushSheets();
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
getXSSFWorkbook().write(os);
|
||||
ZipEntrySource source = null;
|
||||
try {
|
||||
// provide ZipEntrySource to poi which decrypts on the fly
|
||||
source = AesZipFileZipEntrySource.createZipEntrySource(new ByteArrayInputStream(os.toByteArray()));
|
||||
injectData(source, stream);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new IOException(e);
|
||||
} finally {
|
||||
source.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SheetDataWriter createSheetDataWriter() throws IOException {
|
||||
//log values to ensure these values are accessible to subclasses
|
||||
logger.log(POILogger.INFO, "isCompressTempFiles: " + isCompressTempFiles());
|
||||
logger.log(POILogger.INFO, "SharedStringSource: " + getSharedStringSource());
|
||||
return new SheetDataWriterWithDecorator();
|
||||
}
|
||||
}
|
||||
|
||||
static class SheetDataWriterWithDecorator extends SheetDataWriter {
|
||||
final static CipherAlgorithm cipherAlgorithm = CipherAlgorithm.aes128;
|
||||
SecretKeySpec skeySpec;
|
||||
byte[] ivBytes;
|
||||
|
||||
public SheetDataWriterWithDecorator() throws IOException {
|
||||
super();
|
||||
}
|
||||
|
||||
void init() {
|
||||
if(skeySpec == null) {
|
||||
SecureRandom sr = new SecureRandom();
|
||||
ivBytes = new byte[16];
|
||||
byte[] keyBytes = new byte[16];
|
||||
sr.nextBytes(ivBytes);
|
||||
sr.nextBytes(keyBytes);
|
||||
skeySpec = new SecretKeySpec(keyBytes, cipherAlgorithm.jceId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OutputStream decorateOutputStream(FileOutputStream fos) {
|
||||
init();
|
||||
Cipher ciEnc = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.ENCRYPT_MODE, "PKCS5Padding");
|
||||
return new CipherOutputStream(fos, ciEnc);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InputStream decorateInputStream(FileInputStream fis) {
|
||||
Cipher ciDec = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.DECRYPT_MODE, "PKCS5Padding");
|
||||
return new CipherInputStream(fis, ciDec);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue