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;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
|
@ -69,8 +69,10 @@ public class AesZipFileZipEntrySource implements ZipEntrySource {
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if(!closed) {
|
||||
zipFile.close();
|
||||
tmpFile.delete();
|
||||
}
|
||||
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;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user