Line 0
Link Here
|
|
|
1 |
/* ==================================================================== |
2 |
Licensed to the Apache Software Foundation (ASF) under one or more |
3 |
contributor license agreements. See the NOTICE file distributed with |
4 |
this work for additional information regarding copyright ownership. |
5 |
The ASF licenses this file to You under the Apache License, Version 2.0 |
6 |
(the "License"); you may not use this file except in compliance with |
7 |
the License. You may obtain a copy of the License at |
8 |
|
9 |
http://www.apache.org/licenses/LICENSE-2.0 |
10 |
|
11 |
Unless required by applicable law or agreed to in writing, software |
12 |
distributed under the License is distributed on an "AS IS" BASIS, |
13 |
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
14 |
See the License for the specific language governing permissions and |
15 |
limitations under the License. |
16 |
==================================================================== */ |
17 |
|
18 |
package org.apache.poi.poifs.crypt; |
19 |
|
20 |
import static org.junit.Assert.assertEquals; |
21 |
import static org.junit.Assert.assertTrue; |
22 |
|
23 |
import java.io.File; |
24 |
import java.io.FileInputStream; |
25 |
import java.io.FileOutputStream; |
26 |
import java.io.FilterOutputStream; |
27 |
import java.io.IOException; |
28 |
import java.io.InputStream; |
29 |
import java.security.GeneralSecurityException; |
30 |
import java.security.SecureRandom; |
31 |
import java.util.Enumeration; |
32 |
import java.util.zip.ZipEntry; |
33 |
import java.util.zip.ZipException; |
34 |
import java.util.zip.ZipFile; |
35 |
import java.util.zip.ZipInputStream; |
36 |
import java.util.zip.ZipOutputStream; |
37 |
|
38 |
import javax.crypto.Cipher; |
39 |
import javax.crypto.CipherInputStream; |
40 |
import javax.crypto.CipherOutputStream; |
41 |
import javax.crypto.spec.SecretKeySpec; |
42 |
|
43 |
import org.apache.poi.openxml4j.exceptions.InvalidFormatException; |
44 |
import org.apache.poi.openxml4j.exceptions.OpenXML4JException; |
45 |
import org.apache.poi.openxml4j.opc.OPCPackage; |
46 |
import org.apache.poi.openxml4j.util.ZipEntrySource; |
47 |
import org.apache.poi.poifs.filesystem.POIFSFileSystem; |
48 |
import org.apache.poi.util.IOUtils; |
49 |
import org.apache.poi.xssf.XSSFTestDataSamples; |
50 |
import org.apache.poi.xssf.extractor.XSSFEventBasedExcelExtractor; |
51 |
import org.apache.poi.xssf.usermodel.XSSFWorkbook; |
52 |
import org.apache.xmlbeans.XmlException; |
53 |
import org.junit.Test; |
54 |
|
55 |
public class TestSecureTempZip { |
56 |
@Test |
57 |
public void protectedTempZip() throws IOException, GeneralSecurityException, XmlException, OpenXML4JException { |
58 |
final File tmpFile = new File("build/tmp", "protectedXlsx.zip"); |
59 |
File tikaProt = XSSFTestDataSamples.getSampleFile("protected_passtika.xlsx"); |
60 |
FileInputStream fis = new FileInputStream(tikaProt); |
61 |
POIFSFileSystem poifs = new POIFSFileSystem(fis); |
62 |
EncryptionInfo ei = new EncryptionInfo(poifs); |
63 |
Decryptor dec = ei.getDecryptor(); |
64 |
boolean passOk = dec.verifyPassword("tika"); |
65 |
assertTrue(passOk); |
66 |
|
67 |
// generate session key |
68 |
SecureRandom sr = new SecureRandom(); |
69 |
byte[] ivBytes = new byte[16], keyBytes = new byte[16]; |
70 |
sr.nextBytes(ivBytes); |
71 |
sr.nextBytes(keyBytes); |
72 |
|
73 |
// extract encrypted ooxml file and write to custom encrypted zip file |
74 |
InputStream is = dec.getDataStream(poifs); |
75 |
copyToFile(is, tmpFile, CipherAlgorithm.aes128, keyBytes, ivBytes); |
76 |
is.close(); |
77 |
|
78 |
// provide ZipEntrySource to poi which decrypts on the fly |
79 |
ZipEntrySource source = fileToSource(tmpFile, CipherAlgorithm.aes128, keyBytes, ivBytes); |
80 |
|
81 |
// test the source |
82 |
OPCPackage opc = OPCPackage.open(source); |
83 |
String expected = "This is an Encrypted Excel spreadsheet."; |
84 |
|
85 |
XSSFEventBasedExcelExtractor extractor = new XSSFEventBasedExcelExtractor(opc); |
86 |
extractor.setIncludeSheetNames(false); |
87 |
String txt = extractor.getText(); |
88 |
assertEquals(expected, txt.trim()); |
89 |
|
90 |
XSSFWorkbook wb = new XSSFWorkbook(opc); |
91 |
txt = wb.getSheetAt(0).getRow(0).getCell(0).getStringCellValue(); |
92 |
assertEquals(expected, txt); |
93 |
|
94 |
extractor.close(); |
95 |
|
96 |
wb.close(); |
97 |
opc.close(); |
98 |
source.close(); |
99 |
poifs.close(); |
100 |
fis.close(); |
101 |
} |
102 |
|
103 |
private void copyToFile(InputStream is, File tmpFile, CipherAlgorithm cipherAlgorithm, byte keyBytes[], byte ivBytes[]) throws IOException, GeneralSecurityException { |
104 |
SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, cipherAlgorithm.jceId); |
105 |
Cipher ciEnc = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.ENCRYPT_MODE, "PKCS5Padding"); |
106 |
|
107 |
ZipInputStream zis = new ZipInputStream(is); |
108 |
FileOutputStream fos = new FileOutputStream(tmpFile); |
109 |
ZipOutputStream zos = new ZipOutputStream(fos); |
110 |
|
111 |
ZipEntry ze; |
112 |
while ((ze = zis.getNextEntry()) != null) { |
113 |
// the cipher output stream pads the data, therefore we can't reuse the ZipEntry with set sizes |
114 |
// as those will be validated upon close() |
115 |
ZipEntry zeNew = new ZipEntry(ze.getName()); |
116 |
zeNew.setComment(ze.getComment()); |
117 |
zeNew.setExtra(ze.getExtra()); |
118 |
zeNew.setTime(ze.getTime()); |
119 |
// zeNew.setMethod(ze.getMethod()); |
120 |
zos.putNextEntry(zeNew); |
121 |
FilterOutputStream fos2 = new FilterOutputStream(zos){ |
122 |
// don't close underlying ZipOutputStream |
123 |
public void close() {} |
124 |
}; |
125 |
CipherOutputStream cos = new CipherOutputStream(fos2, ciEnc); |
126 |
IOUtils.copy(zis, cos); |
127 |
cos.close(); |
128 |
fos2.close(); |
129 |
zos.closeEntry(); |
130 |
zis.closeEntry(); |
131 |
} |
132 |
zos.close(); |
133 |
fos.close(); |
134 |
zis.close(); |
135 |
} |
136 |
|
137 |
private ZipEntrySource fileToSource(File tmpFile, CipherAlgorithm cipherAlgorithm, byte keyBytes[], byte ivBytes[]) throws ZipException, IOException { |
138 |
SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, cipherAlgorithm.jceId); |
139 |
Cipher ciDec = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.DECRYPT_MODE, "PKCS5Padding"); |
140 |
ZipFile zf = new ZipFile(tmpFile); |
141 |
return new AesZipFileZipEntrySource(zf, ciDec); |
142 |
} |
143 |
|
144 |
static class AesZipFileZipEntrySource implements ZipEntrySource { |
145 |
final ZipFile zipFile; |
146 |
final Cipher ci; |
147 |
|
148 |
AesZipFileZipEntrySource(ZipFile zipFile, Cipher ci) { |
149 |
this.zipFile = zipFile; |
150 |
this.ci = ci; |
151 |
} |
152 |
|
153 |
/** |
154 |
* Note: the file sizes are rounded up to the next cipher block size, |
155 |
* so don't rely on file sizes of these custom encrypted zip file entries! |
156 |
*/ |
157 |
public Enumeration<? extends ZipEntry> getEntries() { |
158 |
return zipFile.entries(); |
159 |
} |
160 |
|
161 |
@SuppressWarnings("resource") |
162 |
public InputStream getInputStream(ZipEntry entry) throws IOException { |
163 |
InputStream is = zipFile.getInputStream(entry); |
164 |
return new CipherInputStream(is, ci); |
165 |
} |
166 |
|
167 |
@Override |
168 |
public void close() throws IOException { |
169 |
zipFile.close(); |
170 |
} |
171 |
} |
172 |
} |
173 |
native |