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:
Javen O'Neal 2016-10-09 12:58:46 +00:00
parent db53120ab3
commit 02b3019f6c
4 changed files with 159 additions and 10 deletions

View File

@ -16,6 +16,7 @@
==================================================================== */
package org.apache.poi.openxml4j.util;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
@ -28,7 +29,7 @@ import java.util.zip.ZipEntry;
* needing to worry about ZipFile vs ZipInputStream
* being annoyingly very different.
*/
public interface ZipEntrySource {
public interface ZipEntrySource extends Closeable {
/**
* Returns an Enumeration of all the Entries
*/
@ -44,6 +45,7 @@ public interface ZipEntrySource {
* Indicates we are done with reading, and
* resources may be freed
*/
@Override
public void close() throws IOException;
/**

View File

@ -69,8 +69,10 @@ public class AesZipFileZipEntrySource implements ZipEntrySource {
@Override
public void close() throws IOException {
if(!closed) {
zipFile.close();
tmpFile.delete();
}
closed = true;
}

View File

@ -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());
}
}
}

View File

@ -19,10 +19,14 @@
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.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
@ -30,19 +34,25 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
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.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.IOUtils;
import org.apache.poi.util.POILogFactory;
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.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
@ -55,10 +65,12 @@ import org.junit.Test;
*/
public final class TestSXSSFWorkbookWithCustomZipEntrySource {
@Test
public void customZipEntrySource() throws IOException, GeneralSecurityException {
final String sheetName = "TestSheet1";
final String cellValue = "customZipEntrySource";
// write an unencrypted workbook to disk, but any temporary files are encrypted
@Test
public void customZipEntrySource() throws IOException {
SXSSFWorkbookWithCustomZipEntrySource workbook = new SXSSFWorkbookWithCustomZipEntrySource();
SXSSFSheet sheet1 = workbook.createSheet(sheetName);
SXSSFRow row1 = sheet1.createRow(1);
@ -77,6 +89,54 @@ public final class TestSXSSFWorkbookWithCustomZipEntrySource {
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 {
private static final POILogger logger = POILogFactory.getLogger(SXSSFWorkbookWithCustomZipEntrySource.class);
@ -84,17 +144,19 @@ public final class TestSXSSFWorkbookWithCustomZipEntrySource {
@Override
public void write(OutputStream stream) throws IOException {
flushSheets();
ByteArrayOutputStream os = new ByteArrayOutputStream();
EncryptedTempData tempData = new EncryptedTempData();
OutputStream os = tempData.getOutputStream();
getXSSFWorkbook().write(os);
os.close();
ZipEntrySource source = null;
try {
// provide ZipEntrySource to poi which decrypts on the fly
source = AesZipFileZipEntrySource.createZipEntrySource(new ByteArrayInputStream(os.toByteArray()));
source = AesZipFileZipEntrySource.createZipEntrySource(tempData.getInputStream());
injectData(source, stream);
} catch (GeneralSecurityException e) {
throw new IOException(e);
} finally {
source.close();
IOUtils.closeQuietly(source);
}
}
@ -107,6 +169,7 @@ public final class TestSXSSFWorkbookWithCustomZipEntrySource {
}
}
static class SheetDataWriterWithDecorator extends SheetDataWriter {
final static CipherAlgorithm cipherAlgorithm = CipherAlgorithm.aes128;
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();
}
}
}