bug 60153: patch from PJ Fanning to demonstrate that SXSSFWorkbook SheetDataWriter can write encrypted temporary files to disk and the workbook can be AES encrypted when written to disk for a fully secure disk environment
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1763969 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
db53120ab3
commit
02b3019f6c
@ -16,6 +16,7 @@
|
|||||||
==================================================================== */
|
==================================================================== */
|
||||||
package org.apache.poi.openxml4j.util;
|
package org.apache.poi.openxml4j.util;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
@ -28,7 +29,7 @@ import java.util.zip.ZipEntry;
|
|||||||
* needing to worry about ZipFile vs ZipInputStream
|
* needing to worry about ZipFile vs ZipInputStream
|
||||||
* being annoyingly very different.
|
* being annoyingly very different.
|
||||||
*/
|
*/
|
||||||
public interface ZipEntrySource {
|
public interface ZipEntrySource extends Closeable {
|
||||||
/**
|
/**
|
||||||
* Returns an Enumeration of all the Entries
|
* Returns an Enumeration of all the Entries
|
||||||
*/
|
*/
|
||||||
@ -44,6 +45,7 @@ public interface ZipEntrySource {
|
|||||||
* Indicates we are done with reading, and
|
* Indicates we are done with reading, and
|
||||||
* resources may be freed
|
* resources may be freed
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void close() throws IOException;
|
public void close() throws IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -69,8 +69,10 @@ public class AesZipFileZipEntrySource implements ZipEntrySource {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
zipFile.close();
|
if(!closed) {
|
||||||
tmpFile.delete();
|
zipFile.close();
|
||||||
|
tmpFile.delete();
|
||||||
|
}
|
||||||
closed = true;
|
closed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
/* ====================================================================
|
||||||
|
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 java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.poi.xssf.streaming.TestSXSSFWorkbookWithCustomZipEntrySource.SXSSFWorkbookWithCustomZipEntrySource;
|
||||||
|
import org.apache.poi.xssf.streaming.TestSXSSFWorkbookWithCustomZipEntrySource.SheetDataWriterWithDecorator;
|
||||||
|
|
||||||
|
// a class to record a list of temporary files that are written to disk
|
||||||
|
// afterwards, a test function can check whether these files were encrypted or not
|
||||||
|
public class TempFileRecordingSXSSFWorkbookWithCustomZipEntrySource extends SXSSFWorkbookWithCustomZipEntrySource {
|
||||||
|
|
||||||
|
private final List<File> tempFiles = new ArrayList<File>();
|
||||||
|
|
||||||
|
List<File> getTempFiles() {
|
||||||
|
return new ArrayList<File>(tempFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SheetDataWriter createSheetDataWriter() throws IOException {
|
||||||
|
return new TempFileRecordingSheetDataWriterWithDecorator();
|
||||||
|
}
|
||||||
|
|
||||||
|
class TempFileRecordingSheetDataWriterWithDecorator extends SheetDataWriterWithDecorator {
|
||||||
|
|
||||||
|
TempFileRecordingSheetDataWriterWithDecorator() throws IOException {
|
||||||
|
super();
|
||||||
|
tempFiles.add(getTempFile());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,10 +19,14 @@
|
|||||||
|
|
||||||
package org.apache.poi.xssf.streaming;
|
package org.apache.poi.xssf.streaming;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -30,19 +34,25 @@ import java.io.InputStream;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.CipherInputStream;
|
import javax.crypto.CipherInputStream;
|
||||||
import javax.crypto.CipherOutputStream;
|
import javax.crypto.CipherOutputStream;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
|
||||||
|
import org.apache.poi.openxml4j.opc.OPCPackage;
|
||||||
import org.apache.poi.openxml4j.util.ZipEntrySource;
|
import org.apache.poi.openxml4j.util.ZipEntrySource;
|
||||||
import org.apache.poi.poifs.crypt.AesZipFileZipEntrySource;
|
import org.apache.poi.poifs.crypt.AesZipFileZipEntrySource;
|
||||||
import org.apache.poi.poifs.crypt.ChainingMode;
|
import org.apache.poi.poifs.crypt.ChainingMode;
|
||||||
import org.apache.poi.poifs.crypt.CipherAlgorithm;
|
import org.apache.poi.poifs.crypt.CipherAlgorithm;
|
||||||
import org.apache.poi.poifs.crypt.CryptoFunctions;
|
import org.apache.poi.poifs.crypt.CryptoFunctions;
|
||||||
|
import org.apache.poi.util.IOUtils;
|
||||||
import org.apache.poi.util.POILogFactory;
|
import org.apache.poi.util.POILogFactory;
|
||||||
import org.apache.poi.util.POILogger;
|
import org.apache.poi.util.POILogger;
|
||||||
|
import org.apache.poi.util.TempFile;
|
||||||
import org.apache.poi.xssf.usermodel.XSSFCell;
|
import org.apache.poi.xssf.usermodel.XSSFCell;
|
||||||
import org.apache.poi.xssf.usermodel.XSSFRow;
|
import org.apache.poi.xssf.usermodel.XSSFRow;
|
||||||
import org.apache.poi.xssf.usermodel.XSSFSheet;
|
import org.apache.poi.xssf.usermodel.XSSFSheet;
|
||||||
@ -54,11 +64,13 @@ import org.junit.Test;
|
|||||||
* is encrypted, but the final saved workbook is not encrypted
|
* is encrypted, but the final saved workbook is not encrypted
|
||||||
*/
|
*/
|
||||||
public final class TestSXSSFWorkbookWithCustomZipEntrySource {
|
public final class TestSXSSFWorkbookWithCustomZipEntrySource {
|
||||||
|
|
||||||
|
final String sheetName = "TestSheet1";
|
||||||
|
final String cellValue = "customZipEntrySource";
|
||||||
|
|
||||||
|
// write an unencrypted workbook to disk, but any temporary files are encrypted
|
||||||
@Test
|
@Test
|
||||||
public void customZipEntrySource() throws IOException, GeneralSecurityException {
|
public void customZipEntrySource() throws IOException {
|
||||||
final String sheetName = "TestSheet1";
|
|
||||||
final String cellValue = "customZipEntrySource";
|
|
||||||
SXSSFWorkbookWithCustomZipEntrySource workbook = new SXSSFWorkbookWithCustomZipEntrySource();
|
SXSSFWorkbookWithCustomZipEntrySource workbook = new SXSSFWorkbookWithCustomZipEntrySource();
|
||||||
SXSSFSheet sheet1 = workbook.createSheet(sheetName);
|
SXSSFSheet sheet1 = workbook.createSheet(sheetName);
|
||||||
SXSSFRow row1 = sheet1.createRow(1);
|
SXSSFRow row1 = sheet1.createRow(1);
|
||||||
@ -77,6 +89,54 @@ public final class TestSXSSFWorkbookWithCustomZipEntrySource {
|
|||||||
xwb.close();
|
xwb.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// write an encrypted workbook to disk, and encrypt any temporary files as well
|
||||||
|
@Test
|
||||||
|
public void customZipEntrySourceForWriteAndRead() throws IOException, GeneralSecurityException, InvalidFormatException {
|
||||||
|
SXSSFWorkbookWithCustomZipEntrySource workbook = new SXSSFWorkbookWithCustomZipEntrySource();
|
||||||
|
SXSSFSheet sheet1 = workbook.createSheet(sheetName);
|
||||||
|
SXSSFRow row1 = sheet1.createRow(1);
|
||||||
|
SXSSFCell cell1 = row1.createCell(1);
|
||||||
|
cell1.setCellValue(cellValue);
|
||||||
|
EncryptedTempData tempData = new EncryptedTempData();
|
||||||
|
workbook.write(tempData.getOutputStream());
|
||||||
|
workbook.close();
|
||||||
|
workbook.dispose();
|
||||||
|
ZipEntrySource zipEntrySource = AesZipFileZipEntrySource.createZipEntrySource(tempData.getInputStream());
|
||||||
|
tempData.dispose();
|
||||||
|
OPCPackage opc = OPCPackage.open(zipEntrySource);
|
||||||
|
XSSFWorkbook xwb = new XSSFWorkbook(opc);
|
||||||
|
zipEntrySource.close();
|
||||||
|
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();
|
||||||
|
opc.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validateTempFilesAreEncrypted() throws IOException {
|
||||||
|
TempFileRecordingSXSSFWorkbookWithCustomZipEntrySource workbook = new TempFileRecordingSXSSFWorkbookWithCustomZipEntrySource();
|
||||||
|
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();
|
||||||
|
List<File> tempFiles = workbook.getTempFiles();
|
||||||
|
assertEquals(1, tempFiles.size());
|
||||||
|
File tempFile = tempFiles.get(0);
|
||||||
|
assertTrue("tempFile exists?", tempFile.exists());
|
||||||
|
byte[] data = IOUtils.toByteArray(new FileInputStream(tempFile));
|
||||||
|
String text = new String(data, UTF_8);
|
||||||
|
assertFalse(text.contains(sheetName));
|
||||||
|
assertFalse(text.contains(cellValue));
|
||||||
|
workbook.dispose();
|
||||||
|
assertFalse("tempFile deleted after dispose?", tempFile.exists());
|
||||||
|
}
|
||||||
|
|
||||||
static class SXSSFWorkbookWithCustomZipEntrySource extends SXSSFWorkbook {
|
static class SXSSFWorkbookWithCustomZipEntrySource extends SXSSFWorkbook {
|
||||||
|
|
||||||
private static final POILogger logger = POILogFactory.getLogger(SXSSFWorkbookWithCustomZipEntrySource.class);
|
private static final POILogger logger = POILogFactory.getLogger(SXSSFWorkbookWithCustomZipEntrySource.class);
|
||||||
@ -84,17 +144,19 @@ public final class TestSXSSFWorkbookWithCustomZipEntrySource {
|
|||||||
@Override
|
@Override
|
||||||
public void write(OutputStream stream) throws IOException {
|
public void write(OutputStream stream) throws IOException {
|
||||||
flushSheets();
|
flushSheets();
|
||||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
EncryptedTempData tempData = new EncryptedTempData();
|
||||||
|
OutputStream os = tempData.getOutputStream();
|
||||||
getXSSFWorkbook().write(os);
|
getXSSFWorkbook().write(os);
|
||||||
|
os.close();
|
||||||
ZipEntrySource source = null;
|
ZipEntrySource source = null;
|
||||||
try {
|
try {
|
||||||
// provide ZipEntrySource to poi which decrypts on the fly
|
// provide ZipEntrySource to poi which decrypts on the fly
|
||||||
source = AesZipFileZipEntrySource.createZipEntrySource(new ByteArrayInputStream(os.toByteArray()));
|
source = AesZipFileZipEntrySource.createZipEntrySource(tempData.getInputStream());
|
||||||
injectData(source, stream);
|
injectData(source, stream);
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
} finally {
|
} finally {
|
||||||
source.close();
|
IOUtils.closeQuietly(source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,6 +169,7 @@ public final class TestSXSSFWorkbookWithCustomZipEntrySource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static class SheetDataWriterWithDecorator extends SheetDataWriter {
|
static class SheetDataWriterWithDecorator extends SheetDataWriter {
|
||||||
final static CipherAlgorithm cipherAlgorithm = CipherAlgorithm.aes128;
|
final static CipherAlgorithm cipherAlgorithm = CipherAlgorithm.aes128;
|
||||||
SecretKeySpec skeySpec;
|
SecretKeySpec skeySpec;
|
||||||
@ -141,4 +204,37 @@ public final class TestSXSSFWorkbookWithCustomZipEntrySource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// a class to save and read an AES-encrypted workbook
|
||||||
|
static class EncryptedTempData {
|
||||||
|
final static CipherAlgorithm cipherAlgorithm = CipherAlgorithm.aes128;
|
||||||
|
final SecretKeySpec skeySpec;
|
||||||
|
final byte[] ivBytes;
|
||||||
|
final File tempFile;
|
||||||
|
|
||||||
|
EncryptedTempData() throws IOException {
|
||||||
|
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);
|
||||||
|
tempFile = TempFile.createTempFile("poi-temp-data", ".tmp");
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputStream getOutputStream() throws IOException {
|
||||||
|
Cipher ciEnc = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.ENCRYPT_MODE, null);
|
||||||
|
return new CipherOutputStream(new FileOutputStream(tempFile), ciEnc);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream getInputStream() throws IOException {
|
||||||
|
Cipher ciDec = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.DECRYPT_MODE, null);
|
||||||
|
return new CipherInputStream(new FileInputStream(tempFile), ciDec);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
tempFile.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user