View | Details | Raw Unified | Return to bug 59841
Collapse All | Expand All

(-)src/ooxml/java/org/apache/poi/openxml4j/opc/OPCPackage.java (+37 lines)
Lines 53-58 Link Here
53
import org.apache.poi.openxml4j.opc.internal.unmarshallers.PackagePropertiesUnmarshaller;
53
import org.apache.poi.openxml4j.opc.internal.unmarshallers.PackagePropertiesUnmarshaller;
54
import org.apache.poi.openxml4j.opc.internal.unmarshallers.UnmarshallContext;
54
import org.apache.poi.openxml4j.opc.internal.unmarshallers.UnmarshallContext;
55
import org.apache.poi.openxml4j.util.Nullable;
55
import org.apache.poi.openxml4j.util.Nullable;
56
import org.apache.poi.openxml4j.util.ZipEntrySource;
56
import org.apache.poi.util.NotImplemented;
57
import org.apache.poi.util.NotImplemented;
57
import org.apache.poi.util.POILogFactory;
58
import org.apache.poi.util.POILogFactory;
58
import org.apache.poi.util.POILogger;
59
import org.apache.poi.util.POILogger;
Lines 200-205 Link Here
200
      return open(file, defaultPackageAccess);
201
      return open(file, defaultPackageAccess);
201
   }
202
   }
202
203
204
   /**
205
    * Open an user provided {@link ZipEntrySource} with read-only permission.
206
    * This method can be used to stream data into POI.
207
    * Opposed to other open variants, the data is read as-is, e.g. there aren't
208
    * any zip-bomb protection put in place.
209
    *
210
    * @param zipEntry the custom source
211
    * @return A Package object
212
    * @throws InvalidFormatException if a parsing error occur.
213
    */
214
   public static OPCPackage open(ZipEntrySource zipEntry)
215
   throws InvalidFormatException {
216
       OPCPackage pack = new ZipPackage(zipEntry, PackageAccess.READ);
217
       try {
218
           if (pack.partList == null) {
219
               pack.getParts();
220
           }
221
           // pack.originalPackagePath = file.getAbsolutePath();
222
           return pack;
223
       } catch (InvalidFormatException e) {
224
           try {
225
               pack.close();
226
           } catch (IOException e1) {
227
               throw new IllegalStateException(e);
228
           }
229
           throw e;
230
       } catch (RuntimeException e) {
231
           try {
232
               pack.close();
233
           } catch (IOException e1) {
234
               throw new IllegalStateException(e);
235
           }
236
           throw e;
237
       }
238
   }
239
   
203
	/**
240
	/**
204
	 * Open a package.
241
	 * Open a package.
205
	 *
242
	 *
(-)src/ooxml/java/org/apache/poi/xssf/eventusermodel/ReadOnlySharedStringsTable.java (-2 / +6 lines)
Lines 20-25 Link Here
20
20
21
import java.io.IOException;
21
import java.io.IOException;
22
import java.io.InputStream;
22
import java.io.InputStream;
23
import java.io.PushbackInputStream;
23
import java.util.ArrayList;
24
import java.util.ArrayList;
24
import java.util.List;
25
import java.util.List;
25
26
Lines 140-147 Link Here
140
     * @throws ParserConfigurationException
141
     * @throws ParserConfigurationException
141
     */
142
     */
142
    public void readFrom(InputStream is) throws IOException, SAXException {
143
    public void readFrom(InputStream is) throws IOException, SAXException {
143
        if (is.available() > 0) {
144
        PushbackInputStream pis = new PushbackInputStream(is, 1);
144
            InputSource sheetSource = new InputSource(is);
145
        int emptyTest = pis.read();
146
        if (emptyTest > -1) {
147
            pis.unread(emptyTest);
148
            InputSource sheetSource = new InputSource(pis);
145
            try {
149
            try {
146
                XMLReader sheetParser = SAXHelper.newXMLReader();
150
                XMLReader sheetParser = SAXHelper.newXMLReader();
147
                sheetParser.setContentHandler(this);
151
                sheetParser.setContentHandler(this);
(-)src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSecureTempZip.java (+173 lines)
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

Return to bug 59841