From 662a34291b305f50dbeee557ef28800f6d6a7f55 Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Mon, 3 Jul 2006 20:34:41 +0000 Subject: [PATCH] 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 --- .../apache/poi/hslf/EncryptedSlideShow.java | 127 +++++++++++++++ .../org/apache/poi/hslf/HSLFSlideShow.java | 154 +++++++++++------- .../EncryptedPowerPointFileException.java | 34 ++++ .../hslf/record/DocumentEncryptionAtom.java | 101 ++++++++++++ .../org/apache/poi/hslf/record/Record.java | 17 ++ .../apache/poi/hslf/record/RecordTypes.java | 3 + .../apache/poi/hslf/TestEncryptedFile.java | 81 +++++++++ .../hslf/data/Password_Protected-56-hello.ppt | Bin 0 -> 10752 bytes .../hslf/data/Password_Protected-hello.ppt | Bin 0 -> 10752 bytes .../hslf/data/Password_Protected-np-hello.ppt | Bin 0 -> 10752 bytes .../record/TestDocumentEncryptionAtom.java | 107 ++++++++++++ 11 files changed, 564 insertions(+), 60 deletions(-) create mode 100644 src/scratchpad/src/org/apache/poi/hslf/EncryptedSlideShow.java create mode 100644 src/scratchpad/src/org/apache/poi/hslf/exceptions/EncryptedPowerPointFileException.java create mode 100644 src/scratchpad/src/org/apache/poi/hslf/record/DocumentEncryptionAtom.java create mode 100644 src/scratchpad/testcases/org/apache/poi/hslf/TestEncryptedFile.java create mode 100644 src/scratchpad/testcases/org/apache/poi/hslf/data/Password_Protected-56-hello.ppt create mode 100644 src/scratchpad/testcases/org/apache/poi/hslf/data/Password_Protected-hello.ppt create mode 100644 src/scratchpad/testcases/org/apache/poi/hslf/data/Password_Protected-np-hello.ppt create mode 100644 src/scratchpad/testcases/org/apache/poi/hslf/record/TestDocumentEncryptionAtom.java diff --git a/src/scratchpad/src/org/apache/poi/hslf/EncryptedSlideShow.java b/src/scratchpad/src/org/apache/poi/hslf/EncryptedSlideShow.java new file mode 100644 index 0000000000..0271abe955 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/EncryptedSlideShow.java @@ -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 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; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/HSLFSlideShow.java b/src/scratchpad/src/org/apache/poi/hslf/HSLFSlideShow.java index ddf9c7520e..9aa5bd9d2a 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/HSLFSlideShow.java +++ b/src/scratchpad/src/org/apache/poi/hslf/HSLFSlideShow.java @@ -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: - // - // - // - // If it has a zero length, following it will be another record - // - // If it has a length, depending on its type it may have children or data - // If it has children, these will follow straight away - // > - // If it has data, this will come straigh after, and run for the length - // - // 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: + // + // + // + // If it has a zero length, following it will be another record + // + // If it has a length, depending on its type it may have children or data + // If it has children, these will follow straight away + // > + // If it has data, this will come straigh after, and run for the length + // + // 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 diff --git a/src/scratchpad/src/org/apache/poi/hslf/exceptions/EncryptedPowerPointFileException.java b/src/scratchpad/src/org/apache/poi/hslf/exceptions/EncryptedPowerPointFileException.java new file mode 100644 index 0000000000..e509d8e27f --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/exceptions/EncryptedPowerPointFileException.java @@ -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); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/DocumentEncryptionAtom.java b/src/scratchpad/src/org/apache/poi/hslf/record/DocumentEncryptionAtom.java new file mode 100644 index 0000000000..84d2319c75 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/record/DocumentEncryptionAtom.java @@ -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); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/Record.java b/src/scratchpad/src/org/apache/poi/hslf/record/Record.java index 0a8906d8b9..1a8470fda6 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/Record.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/Record.java @@ -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 diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java b/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java index e9b7062d72..5117d5f1fd 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java @@ -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; diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/TestEncryptedFile.java b/src/scratchpad/testcases/org/apache/poi/hslf/TestEncryptedFile.java new file mode 100644 index 0000000000..d7d17ae6cd --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hslf/TestEncryptedFile.java @@ -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 + } + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/data/Password_Protected-56-hello.ppt b/src/scratchpad/testcases/org/apache/poi/hslf/data/Password_Protected-56-hello.ppt new file mode 100644 index 0000000000000000000000000000000000000000..d1e5d6617c548b7bf0e580db90badb734cad2364 GIT binary patch literal 10752 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 literal 0 HcmV?d00001 diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/data/Password_Protected-hello.ppt b/src/scratchpad/testcases/org/apache/poi/hslf/data/Password_Protected-hello.ppt new file mode 100644 index 0000000000000000000000000000000000000000..3bbab4d5c0da8cf7ceaf8ebbf737dd98bb4439a8 GIT binary patch literal 10752 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