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/testcases/org/apache/poi/poifs/crypt/TestSecureTempZip.java (+161 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.opc.OPCPackage;
45
import org.apache.poi.openxml4j.util.ZipEntrySource;
46
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
47
import org.apache.poi.util.IOUtils;
48
import org.apache.poi.xssf.XSSFTestDataSamples;
49
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
50
import org.junit.Test;
51
52
public class TestSecureTempZip {
53
    @Test
54
    public void protectedTempZip() throws IOException, GeneralSecurityException, InvalidFormatException {
55
        final File tmpFile = new File("build/tmp", "protectedXlsx.zip");
56
        File tikaProt = XSSFTestDataSamples.getSampleFile("protected_passtika.xlsx");
57
        FileInputStream fis = new FileInputStream(tikaProt);
58
        POIFSFileSystem poifs = new POIFSFileSystem(fis);
59
        EncryptionInfo ei = new EncryptionInfo(poifs);
60
        Decryptor dec = ei.getDecryptor();
61
        boolean passOk = dec.verifyPassword("tika");
62
        assertTrue(passOk);
63
64
        // generate session key
65
        SecureRandom sr = new SecureRandom();
66
        byte[] ivBytes = new byte[16], keyBytes = new byte[16];
67
        sr.nextBytes(ivBytes);
68
        sr.nextBytes(keyBytes);
69
        
70
        // extract encrypted ooxml file and write to custom encrypted zip file 
71
        InputStream is = dec.getDataStream(poifs);
72
        copyToFile(is, tmpFile, CipherAlgorithm.aes128, keyBytes, ivBytes);
73
        is.close();
74
        
75
        // provide ZipEntrySource to poi which decrypts on the fly
76
        ZipEntrySource source = fileToSource(tmpFile, CipherAlgorithm.aes128, keyBytes, ivBytes);
77
78
        // test the source
79
        OPCPackage opc = OPCPackage.open(source);
80
        XSSFWorkbook wb = new XSSFWorkbook(opc);
81
        String txt = wb.getSheetAt(0).getRow(0).getCell(0).getStringCellValue();
82
        assertEquals("This is an Encrypted Excel spreadsheet.", txt);
83
84
        wb.close();
85
        opc.close();
86
        source.close();
87
        poifs.close();
88
        fis.close();
89
    }
90
    
91
    private void copyToFile(InputStream is, File tmpFile, CipherAlgorithm cipherAlgorithm, byte keyBytes[], byte ivBytes[]) throws IOException, GeneralSecurityException {
92
        SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, cipherAlgorithm.jceId);
93
        Cipher ciEnc = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.ENCRYPT_MODE, "PKCS5Padding");
94
95
        ZipInputStream zis = new ZipInputStream(is);
96
        FileOutputStream fos = new FileOutputStream(tmpFile);
97
        ZipOutputStream zos = new ZipOutputStream(fos);
98
99
        ZipEntry ze;
100
        while ((ze = zis.getNextEntry()) != null) {
101
            // the cipher output stream pads the data, therefore we can't reuse the ZipEntry with set sizes
102
            // as those will be validated upon close()
103
            ZipEntry zeNew = new ZipEntry(ze.getName());
104
            zeNew.setComment(ze.getComment());
105
            zeNew.setExtra(ze.getExtra());
106
            zeNew.setTime(ze.getTime());
107
            // zeNew.setMethod(ze.getMethod());
108
            zos.putNextEntry(zeNew);
109
            FilterOutputStream fos2 = new FilterOutputStream(zos){
110
                // don't close underlying ZipOutputStream
111
                public void close() {}
112
            };
113
            CipherOutputStream cos = new CipherOutputStream(fos2, ciEnc);
114
            IOUtils.copy(zis, cos);
115
            cos.close();
116
            fos2.close();
117
            zos.closeEntry();
118
            zis.closeEntry();
119
        }
120
        zos.close();
121
        fos.close();
122
        zis.close();
123
    }
124
    
125
    private ZipEntrySource fileToSource(File tmpFile, CipherAlgorithm cipherAlgorithm, byte keyBytes[], byte ivBytes[]) throws ZipException, IOException {
126
        SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, cipherAlgorithm.jceId);
127
        Cipher ciDec = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.DECRYPT_MODE, "PKCS5Padding");
128
        ZipFile zf = new ZipFile(tmpFile);
129
        return new AesZipFileZipEntrySource(zf, ciDec);
130
    }
131
132
    static class AesZipFileZipEntrySource implements ZipEntrySource {
133
        final ZipFile zipFile;
134
        final Cipher ci;
135
136
        AesZipFileZipEntrySource(ZipFile zipFile, Cipher ci) {
137
            this.zipFile = zipFile;
138
            this.ci = ci;
139
        }
140
141
        /**
142
         * Note: the file sizes are rounded up to the next cipher block size,
143
         * so don't rely on file sizes of these custom encrypted zip file entries!
144
         */
145
        public Enumeration<? extends ZipEntry> getEntries() {
146
            return zipFile.entries();
147
        }
148
149
        @SuppressWarnings("resource")
150
        public InputStream getInputStream(ZipEntry entry) throws IOException {
151
            InputStream is = zipFile.getInputStream(entry);
152
            return new CipherInputStream(is, ci);
153
        }
154
155
        @Override
156
        public void close() throws IOException {
157
            zipFile.close();
158
        }
159
    }
160
}
161
native

Return to bug 59841