Add correct detection for encrypted powerpoint files. An exception will be thrown if they are encountered.\n(As we don't know how the encryption is done, we can't do any decryption of these files)

git-svn-id: https://svn.apache.org/repos/asf/jakarta/poi/trunk@418842 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2006-07-03 20:34:41 +00:00
parent ea6f9d9958
commit 1ea2717c6f
11 changed files with 564 additions and 60 deletions

View File

@ -0,0 +1,127 @@
/* ====================================================================
Copyright 2002-2004 Apache Software Foundation
Licensed 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.hslf;
import java.io.FileNotFoundException;
import org.apache.poi.hslf.record.CurrentUserAtom;
import org.apache.poi.hslf.record.DocumentEncryptionAtom;
import org.apache.poi.hslf.record.PersistPtrHolder;
import org.apache.poi.hslf.record.Record;
import org.apache.poi.hslf.record.UserEditAtom;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.poifs.filesystem.DocumentEntry;
import org.apache.poi.poifs.filesystem.DocumentInputStream;
import org.apache.poi.util.LittleEndian;
/**
* This class provides helper functions for determining if a
* PowerPoint document is Encrypted.
* In future, it may also provide Encryption and Decryption
* functions, but first we'd need to figure out how
* PowerPoint encryption is really done!
*
* @author Nick Burch
*/
public class EncryptedSlideShow
{
/**
* Check to see if a HSLFSlideShow represents an encrypted
* PowerPoint document, or not
* @param hss The HSLFSlideShow to check
* @return true if encrypted, otherwise false
*/
public static boolean checkIfEncrypted(HSLFSlideShow hss) {
// Easy way to check - contains a stream
// "EncryptedSummary"
POIFSFileSystem fs = hss.getPOIFSFileSystem();
try {
fs.getRoot().getEntry("EncryptedSummary");
return true;
} catch(FileNotFoundException fnfe) {
// Doesn't have encrypted properties
}
// If they encrypted the document but not the properties,
// it's harder.
// We need to see what the last record pointed to by the
// first PersistPrtHolder is - if it's a
// DocumentEncryptionAtom, then the file's Encrypted
DocumentEncryptionAtom dea = fetchDocumentEncryptionAtom(hss);
if(dea != null) {
return true;
}
return false;
}
/**
* Return the DocumentEncryptionAtom for a HSLFSlideShow, or
* null if there isn't one.
* @return a DocumentEncryptionAtom, or null if there isn't one
*/
public static DocumentEncryptionAtom fetchDocumentEncryptionAtom(HSLFSlideShow hss) {
// Will be the last Record pointed to by the
// first PersistPrtHolder, if there is one
CurrentUserAtom cua = hss.getCurrentUserAtom();
if(cua.getCurrentEditOffset() != 0) {
// Grab the details of the UserEditAtom there
Record r = Record.buildRecordAtOffset(
hss.getUnderlyingBytes(),
(int)cua.getCurrentEditOffset()
);
if(! (r instanceof UserEditAtom)) { return null; }
UserEditAtom uea = (UserEditAtom)r;
// Now get the PersistPtrHolder
Record r2 = Record.buildRecordAtOffset(
hss.getUnderlyingBytes(),
uea.getPersistPointersOffset()
);
if(! (r2 instanceof PersistPtrHolder)) { return null; }
PersistPtrHolder pph = (PersistPtrHolder)r2;
// Now get the last record
int[] slideIds = pph.getKnownSlideIDs();
int maxSlideId = -1;
for(int i=0; i<slideIds.length; i++) {
if(slideIds[i] > maxSlideId) { maxSlideId = slideIds[i]; }
}
if(maxSlideId == -1) { return null; }
int offset = (
(Integer)pph.getSlideLocationsLookup().get(
new Integer(maxSlideId)
) ).intValue();
Record r3 = Record.buildRecordAtOffset(
hss.getUnderlyingBytes(),
offset
);
// If we have a DocumentEncryptionAtom, it'll be this one
if(r3 instanceof DocumentEncryptionAtom) {
return (DocumentEncryptionAtom)r3;
}
}
return null;
}
}

View File

@ -33,6 +33,7 @@ import org.apache.poi.hpsf.MutablePropertySet;
import org.apache.poi.hpsf.SummaryInformation;
import org.apache.poi.hpsf.DocumentSummaryInformation;
import org.apache.poi.hslf.exceptions.EncryptedPowerPointFileException;
import org.apache.poi.hslf.record.*;
import org.apache.poi.hslf.usermodel.PictureData;
@ -58,6 +59,14 @@ public class HSLFSlideShow extends POIDocument
// Raw Pictures contained in the pictures stream
private PictureData[] _pictures;
/**
* Returns the underlying POIFSFileSystem for the document
* that is open.
*/
protected POIFSFileSystem getPOIFSFileSystem() {
return filesystem;
}
/**
* Constructs a Powerpoint document from fileName. Parses the document
@ -95,15 +104,29 @@ public class HSLFSlideShow extends POIDocument
public HSLFSlideShow(POIFSFileSystem filesystem) throws IOException
{
this.filesystem = filesystem;
// First up, grab the "Current User" stream
// We need this before we can detect Encrypted Documents
readCurrentUserStream();
// Next up, grab the data that makes up the
// PowerPoint stream
readPowerPointStream();
// Check to see if we have an encrypted document,
// bailing out if we do
boolean encrypted = EncryptedSlideShow.checkIfEncrypted(this);
if(encrypted) {
throw new EncryptedPowerPointFileException("Encrypted PowerPoint files are not supported");
}
// Go find a PowerPoint document in the stream
// Save anything useful we come across
readFIB();
// Now, build records based on the PowerPoint stream
buildRecords();
// Look for Property Streams:
readProperties();
// Look for other streams
// Look for any other streams
readOtherStreams();
// Look for Picture Streams:
@ -132,66 +155,70 @@ public class HSLFSlideShow extends POIDocument
}
/**
* Extracts the main document stream from the POI file then hands off
* to other functions that parse other areas.
*
* @throws IOException
*/
private void readFIB() throws IOException
{
// Get the main document stream
DocumentEntry docProps =
(DocumentEntry)filesystem.getRoot().getEntry("PowerPoint Document");
// Grab the document stream
_docstream = new byte[docProps.getSize()];
filesystem.createDocumentInputStream("PowerPoint Document").read(_docstream);
// The format of records in a powerpoint file are:
// <little endian 2 byte "info">
// <little endian 2 byte "type">
// <little endian 4 byte "length">
// If it has a zero length, following it will be another record
// <xx xx yy yy 00 00 00 00> <xx xx yy yy zz zz zz zz>
// If it has a length, depending on its type it may have children or data
// If it has children, these will follow straight away
// <xx xx yy yy zz zz zz zz <xx xx yy yy zz zz zz zz>>
// If it has data, this will come straigh after, and run for the length
// <xx xx yy yy zz zz zz zz dd dd dd dd dd dd dd>
// All lengths given exclude the 8 byte record header
// (Data records are known as Atoms)
// Document should start with:
// 0F 00 E8 03 ## ## ## ##
// (type 1000 = document, info 00 0f is normal, rest is document length)
// 01 00 E9 03 28 00 00 00
// (type 1001 = document atom, info 00 01 normal, 28 bytes long)
// 80 16 00 00 E0 10 00 00 xx xx xx xx xx xx xx xx
// 05 00 00 00 0A 00 00 00 xx xx xx
// (the contents of the document atom, not sure what it means yet)
// (records then follow)
// When parsing a document, look to see if you know about that type
// of the current record. If you know it's a type that has children,
// process the record's data area looking for more records
// If you know about the type and it doesn't have children, either do
// something with the data (eg TextRun) or skip over it
// If you don't know about the type, play safe and skip over it (using
// its length to know where the next record will start)
//
// For now, this work is handled by Record.findChildRecords
_records = Record.findChildRecords(_docstream,0,_docstream.length);
}
/**
* Extracts the main PowerPoint document stream from the
* POI file, ready to be passed
*
* @throws IOException
*/
private void readPowerPointStream() throws IOException
{
// Get the main document stream
DocumentEntry docProps =
(DocumentEntry)filesystem.getRoot().getEntry("PowerPoint Document");
// Grab the document stream
_docstream = new byte[docProps.getSize()];
filesystem.createDocumentInputStream("PowerPoint Document").read(_docstream);
}
/**
* Builds the list of records, based on the contents
* of the PowerPoint stream
*/
private void buildRecords()
{
// The format of records in a powerpoint file are:
// <little endian 2 byte "info">
// <little endian 2 byte "type">
// <little endian 4 byte "length">
// If it has a zero length, following it will be another record
// <xx xx yy yy 00 00 00 00> <xx xx yy yy zz zz zz zz>
// If it has a length, depending on its type it may have children or data
// If it has children, these will follow straight away
// <xx xx yy yy zz zz zz zz <xx xx yy yy zz zz zz zz>>
// If it has data, this will come straigh after, and run for the length
// <xx xx yy yy zz zz zz zz dd dd dd dd dd dd dd>
// All lengths given exclude the 8 byte record header
// (Data records are known as Atoms)
// Document should start with:
// 0F 00 E8 03 ## ## ## ##
// (type 1000 = document, info 00 0f is normal, rest is document length)
// 01 00 E9 03 28 00 00 00
// (type 1001 = document atom, info 00 01 normal, 28 bytes long)
// 80 16 00 00 E0 10 00 00 xx xx xx xx xx xx xx xx
// 05 00 00 00 0A 00 00 00 xx xx xx
// (the contents of the document atom, not sure what it means yet)
// (records then follow)
// When parsing a document, look to see if you know about that type
// of the current record. If you know it's a type that has children,
// process the record's data area looking for more records
// If you know about the type and it doesn't have children, either do
// something with the data (eg TextRun) or skip over it
// If you don't know about the type, play safe and skip over it (using
// its length to know where the next record will start)
//
// For now, this work is handled by Record.findChildRecords
_records = Record.findChildRecords(_docstream,0,_docstream.length);
}
/**
* Find the other from the filesystem (currently just CurrentUserAtom),
* and load them
* Find the "Current User" stream, and load it
*/
public void readOtherStreams() {
// Current User
private void readCurrentUserStream() {
try {
currentUser = new CurrentUserAtom(filesystem);
} catch(IOException ie) {
@ -199,6 +226,13 @@ public class HSLFSlideShow extends POIDocument
currentUser = new CurrentUserAtom();
}
}
/**
* Find any other streams from the filesystem, and load them
*/
private void readOtherStreams() {
// Currently, there aren't any
}
/**
* Find and read in pictures contained in this presentation

View File

@ -0,0 +1,34 @@
/* ====================================================================
Copyright 2002-2004 Apache Software Foundation
Licensed 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.hslf.exceptions;
/**
* This exception is thrown when we try to open a PowerPoint file, and
* discover that it is encrypted
*
* @author Nick Burch
*/
public class EncryptedPowerPointFileException extends IllegalStateException
{
public EncryptedPowerPointFileException(String s) {
super(s);
}
}

View File

@ -0,0 +1,101 @@
/* ====================================================================
Copyright 2002-2004 Apache Software Foundation
Licensed 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.hslf.record;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.StringUtil;
import java.io.IOException;
import java.io.OutputStream;
/**
* A Document Encryption Atom (type 12052). Holds information
* on the Encryption of a Document
*
* @author Nick Burch
*/
public class DocumentEncryptionAtom extends RecordAtom
{
private byte[] _header;
private static long _type = 12052l;
private byte[] data;
private String encryptionProviderName;
/**
* For the Document Encryption Atom
*/
protected DocumentEncryptionAtom(byte[] source, int start, int len) {
// Get the header
_header = new byte[8];
System.arraycopy(source,start,_header,0,8);
// Grab everything else, for now
data = new byte[len-8];
System.arraycopy(source, start+8, data, 0, len-8);
// Grab the provider, from byte 8+44 onwards
// It's a null terminated Little Endian String
int endPos = -1;
int pos = start + 8+44;
while(pos < (start+len) && endPos < 0) {
if(source[pos] == 0 && source[pos+1] == 0) {
// Hit the end
endPos = pos;
}
pos += 2;
}
pos = start + 8+44;
int stringLen = (endPos-pos) / 2;
encryptionProviderName = StringUtil.getFromUnicodeLE(source, pos, stringLen);
}
/**
* Return the length of the encryption key, in bits
*/
public int getKeyLength() {
return data[28];
}
/**
* Return the name of the encryption provider used
*/
public String getEncryptionProviderName() {
return encryptionProviderName;
}
/**
* We are of type 12052
*/
public long getRecordType() { return _type; }
/**
* Write the contents of the record back, so it can be written
* to disk
*/
public void writeOut(OutputStream out) throws IOException {
// Header
out.write(_header);
// Data
out.write(data);
}
}

View File

@ -82,6 +82,23 @@ public abstract class Record
LittleEndian.putShort(bs,s);
o.write(bs);
}
/**
* Build and return the Record at the given offset.
* Note - does less error checking and handling than findChildRecords
* @param b The byte array to build from
* @param offset The offset to build at
*/
public static Record buildRecordAtOffset(byte[] b, int offset) {
long type = LittleEndian.getUShort(b,offset+2);
long rlen = LittleEndian.getUInt(b,offset+4);
// Sanity check the length
int rleni = (int)rlen;
if(rleni < 0) { rleni = 0; }
return createRecordForType(type,b,offset,8+rleni);
}
/**
* Default method for finding child records of a container record

View File

@ -156,6 +156,9 @@ public class RecordTypes {
public static final Type Comment2000Atom = new Type(12001,Comment2000Atom.class);
public static final Type Comment2000Summary = new Type(12004,null);
public static final Type Comment2000SummaryAtom = new Type(12005,null);
// Records ~12050 seem to be related to Document Encryption
public static final Type DocumentEncryptionAtom = new Type(12052,DocumentEncryptionAtom.class);
//records greater then 0xF000 belong to with Microsoft Office Drawing format also known as Escher
public static final int EscherDggContainer = 0xf000;

View File

@ -0,0 +1,81 @@
/* ====================================================================
Copyright 2002-2004 Apache Software Foundation
Licensed 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.hslf;
import junit.framework.TestCase;
import org.apache.poi.hslf.exceptions.EncryptedPowerPointFileException;
import org.apache.poi.hslf.record.*;
/**
* Tests that HSLFSlideShow does the right thing with an encrypted file
*
* @author Nick Burch (nick at torchbox dot com)
*/
public class TestEncryptedFile extends TestCase {
// A non encrypted file
private String ss_ne;
// An encrypted file, with encrypted properties
private String ss_e;
// An encrypted file, without encrypted properties
private String ss_np_e;
// An encrypted file, with a 56 bit key
private String ss_56_e;
public TestEncryptedFile() throws Exception {
String dirname = System.getProperty("HSLF.testdata.path");
ss_ne = dirname + "/basic_test_ppt_file.ppt";
ss_e = dirname + "/Password_Protected-hello.ppt";
ss_np_e = dirname + "/Password_Protected-np-hello.ppt";
ss_56_e = dirname + "/Password_Protected-56-hello.ppt";
}
public void testLoadNonEncrypted() throws Exception {
HSLFSlideShow hss = new HSLFSlideShow(ss_ne);
assertNotNull(hss);
}
public void testLoadEncrypted() throws Exception {
try {
new HSLFSlideShow(ss_e);
fail();
} catch(EncryptedPowerPointFileException e) {
// Good
}
try {
new HSLFSlideShow(ss_np_e);
fail();
} catch(EncryptedPowerPointFileException e) {
// Good
}
try {
new HSLFSlideShow(ss_56_e);
fail();
} catch(EncryptedPowerPointFileException e) {
// Good
}
}
}

View File

@ -0,0 +1,107 @@
/* ====================================================================
Copyright 2002-2004 Apache Software Foundation
Licensed 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.hslf.record;
import junit.framework.TestCase;
/**
* Tests that DocumentEncryptionAtom works properly.
*
* @author Nick Burch (nick at torchbox dot com)
*/
public class TestDocumentEncryptionAtom extends TestCase {
// From a real file
private byte[] data_a = new byte[] {
0x0F, 00, 0x14, 0x2F, 0xBE-256, 00, 00, 00,
02, 00, 02, 00, 0x0C, 00, 00, 00,
0x76, 00, 00, 00, 0x0C, 00, 00, 00,
00, 00, 00, 00, 01, 0x68, 00, 00,
04, 0x80-256, 00, 00, 0x28, 00, 00, 00,
01, 00, 00, 00, 0x30, 00, 0x26, 01,
00, 00, 00, 00,
0x4D, 00, 0x69, 00,
0x63, 00, 0x72, 00, 0x6F, 00, 0x73, 00,
0x6F, 00, 0x66, 00, 0x74, 00, 0x20, 00,
0x42, 00, 0x61, 00, 0x73, 00, 0x65, 00,
0x20, 00, 0x43, 00, 0x72, 00, 0x79, 00,
0x70, 00, 0x74, 00, 0x6F, 00, 0x67, 00,
0x72, 00, 0x61, 00, 0x70, 00, 0x68, 00,
0x69, 00, 0x63, 00, 0x20, 00, 0x50, 00,
0x72, 00, 0x6F, 00, 0x76, 00, 0x69, 00,
0x64, 00, 0x65, 00, 0x72, 00, 0x20, 00,
0x76, 00, 0x31, 00, 0x2E, 00, 0x30, 00,
0x00, 0x00,
0x10, 00, 0x00, 00,
0x62, 0xA6-256,
0xDF-256, 0xEA-256, 0x96-256, 0x84-256,
0xFB-256, 0x89-256, 0x93-256, 0xCA-256,
0xBA-256, 0xEE-256, 0x8E-256, 0x43,
0xC8-256, 0x71, 0xD1-256, 0x89-256,
0xF6-256, 0x4B, 0x2B, 0xD9-256,
0x7E, 0x0B, 0x52, 0xFB-256,
0x68, 0xD7-256, 0x5A, 0x4E, 0x45, 0xDF-256, 0x14, 0x00,
0x00, 0x00, 0x93-256, 0x15, 0x27, 0xEB-256, 0x21, 0x54,
0x7F, 0x0B, 0x56, 0x07, 0xEE-256, 0x66, 0xEB-256, 0x6F,
0xB2-256, 0x8E-256, 0x67, 0x54, 0x07, 0x04, 0x00
};
private byte[] data_b = new byte[] {
15, 0, 20, 47, -66, 0, 0, 0,
2, 0, 2, 0, 4,
0, 0, 0, 118, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0,
0, 1, 104, 0, 0, 4, -128, 0, 0, 56, 0, 0, 0,
1, 0, 0, 0, 48, 0, 38, 1, 0, 0, 0, 0, 77, 0,
105, 0, 99, 0, 114, 0, 111, 0, 115, 0, 111,
0, 102, 0, 116, 0, 32, 0, 66, 0, 97, 0, 115,
0, 101, 0, 32, 0, 67, 0, 114, 0, 121, 0, 112,
0, 116, 0, 111, 0, 103, 0, 114, 0, 97, 0,
112, 0, 104, 0, 105, 0, 99, 0, 32, 0, 80, 0,
114, 0, 111, 0, 118, 0, 105, 0, 100, 0, 101,
0, 114, 0, 32, 0, 118, 0, 49, 0, 46, 0, 48,
0, 0, 0, 16, 0, 0, 0, -80, -66, 112, -40, 57,
110, 54, 80, 64, 61, -73, -29, 48, -35, -20,
17, -40, 84, 54, 6, -103, 125, -22, -72, 53,
103, -114, 13, -48, 111, 29, 78, 20, 0, 0,
0, -97, -67, 55, -62, -94, 14, 15, -21, 37,
3, -104, 22, 6, 102, -61, -98, 62, 40, 61, 21
};
public void testRecordType() throws Exception {
DocumentEncryptionAtom dea1 = new DocumentEncryptionAtom(data_a, 0, data_a.length);
assertEquals(12052l, dea1.getRecordType());
DocumentEncryptionAtom dea2 = new DocumentEncryptionAtom(data_b, 0, data_b.length);
assertEquals(12052l, dea2.getRecordType());
System.out.println(data_a.length);
System.out.println(data_b.length);
}
public void testEncryptionTypeName() throws Exception {
DocumentEncryptionAtom dea1 = new DocumentEncryptionAtom(data_a, 0, data_a.length);
assertEquals("Microsoft Base Cryptographic Provider v1.0", dea1.getEncryptionProviderName());
DocumentEncryptionAtom dea2 = new DocumentEncryptionAtom(data_b, 0, data_b.length);
assertEquals("Microsoft Base Cryptographic Provider v1.0", dea2.getEncryptionProviderName());
}
}