]> source.dussan.org Git - poi.git/commitdiff
Follow-on for bugzilla 47652 - used more specific exception when password is incorrect
authorJosh Micich <josh@apache.org>
Fri, 14 Aug 2009 21:35:44 +0000 (21:35 +0000)
committerJosh Micich <josh@apache.org>
Fri, 14 Aug 2009 21:35:44 +0000 (21:35 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@804381 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java
src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.java
src/testcases/org/apache/poi/hssf/record/AllRecordTests.java
src/testcases/org/apache/poi/hssf/record/TestRecordFactoryInputStream.java [new file with mode: 0644]

index 3cf687e4cbad93d224ba2c4f0ea5441d1db16af6..7ebe5177bd7b364c3f55d6c9d5fb06a5b4cf41f0 100755 (executable)
@@ -23,6 +23,7 @@ import java.util.List;
 import org.apache.poi.hssf.eventusermodel.HSSFEventFactory;
 import org.apache.poi.hssf.eventusermodel.HSSFListener;
 import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
+import org.apache.poi.EncryptedDocumentException;
 
 /**
  * A stream based way to get at complete records, with
@@ -41,7 +42,7 @@ public final class RecordFactoryInputStream {
         * Needed for protected files because each byte is encrypted with respect to its absolute
         * position from the start of the stream.
         */
-       public static final class StreamEncryptionInfo {
+       private static final class StreamEncryptionInfo {
                private final int _initialRecordsSize;
                private final FilePassRecord _filePassRec;
                private final Record _lastRecord;
@@ -97,7 +98,9 @@ public final class RecordFactoryInputStream {
                                key = Biff8EncryptionKey.create(userPassword, fpr.getDocId());
                        }
                        if (!key.validate(fpr.getSaltData(), fpr.getSaltHash())) {
-                               throw new RecordFormatException("Password/docId do not correspond to saltData/saltHash");
+                               throw new EncryptedDocumentException(
+                                               (userPassword == null ? "Default" : "Supplied")
+                                               + " password is invalid for docId/saltData/saltHash");
                        }
                        return new RecordInputStream(original, key, _initialRecordsSize);
                }
index 8a3af7e17fdc6cce121bb99264d71151c43029ef..fd46caf2b5d7c1d1296f6e28408e9d2a6a8d8443 100644 (file)
@@ -90,9 +90,21 @@ public final class Biff8EncryptionKey {
                md5.update(saltDataPrime);
                byte[] finalSaltResult = md5.digest();
 
+               if (false) { // set true to see a valid saltHash value
+                       byte[] saltHashThatWouldWork = xor(saltHash, xor(saltHashPrime, finalSaltResult));
+                       System.out.println(HexDump.toHex(saltHashThatWouldWork));
+               }
+
                return Arrays.equals(saltHashPrime, finalSaltResult);
        }
 
+       private static byte[] xor(byte[] a, byte[] b) {
+               byte[] c = new byte[a.length];
+               for (int i = 0; i < c.length; i++) {
+                       c[i] = (byte) (a[i] ^ b[i]);
+               }
+               return c;
+       }
        private static void check16Bytes(byte[] data, String argName) {
                if (data.length != 16) {
                        throw new IllegalArgumentException("Expected 16 byte " + argName + ", but got " + HexDump.toHex(data));
index 5aec73999a385cffaac84faf11308cde73ef5ffa..920cfe7142323f66250765679fb10d8a3517a381 100755 (executable)
@@ -47,9 +47,11 @@ public final class AllRecordTests {
                result.addTestSuite(TestBOFRecord.class);
                result.addTestSuite(TestBoolErrRecord.class);
                result.addTestSuite(TestBoundSheetRecord.class);
+               result.addTestSuite(TestCellRange.class);
                result.addTestSuite(TestCFHeaderRecord.class);
                result.addTestSuite(TestCFRuleRecord.class);
                result.addTestSuite(TestCommonObjectDataSubRecord.class);
+               result.addTestSuite(TestConstantValueParser.class);
                result.addTestSuite(TestDrawingGroupRecord.class);
                result.addTestSuite(TestEmbeddedObjectRefSubRecord.class);
                result.addTestSuite(TestEndSubRecord.class);
@@ -67,8 +69,9 @@ public final class AllRecordTests {
                result.addTestSuite(TestObjRecord.class);
                result.addTestSuite(TestPaletteRecord.class);
                result.addTestSuite(TestPaneRecord.class);
-               result.addTestSuite(TestRecordInputStream.class);
                result.addTestSuite(TestRecordFactory.class);
+               result.addTestSuite(TestRecordFactoryInputStream.class);
+               result.addTestSuite(TestRecordInputStream.class);
                result.addTestSuite(TestSCLRecord.class);
                result.addTestSuite(TestSSTDeserializer.class);
                result.addTestSuite(TestSSTRecord.class);
@@ -84,8 +87,6 @@ public final class AllRecordTests {
                result.addTestSuite(TestUnicodeNameRecord.class);
                result.addTestSuite(TestUnicodeString.class);
                result.addTestSuite(TestWriteAccessRecord.class);
-               result.addTestSuite(TestCellRange.class);
-               result.addTestSuite(TestConstantValueParser.class);
                return result;
        }
 }
diff --git a/src/testcases/org/apache/poi/hssf/record/TestRecordFactoryInputStream.java b/src/testcases/org/apache/poi/hssf/record/TestRecordFactoryInputStream.java
new file mode 100644 (file)
index 0000000..a97bb5f
--- /dev/null
@@ -0,0 +1,147 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You 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.hssf.record;
+
+import java.io.ByteArrayInputStream;
+import java.util.Arrays;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
+import org.apache.poi.util.HexRead;
+
+/**
+ * Tests for {@link RecordFactoryInputStream}
+ *
+ * @author Josh Micich
+ */
+public final class TestRecordFactoryInputStream extends TestCase {
+
+       /**
+        * Hex dump of a BOF record and most of a FILEPASS record.
+        * A 16 byte saltHash should be added to complete the second record
+        */
+       private static final String COMMON_HEX_DATA = ""
+               // BOF
+               + "09 08 10 00"
+               + "00 06  05 00  D3 10  CC 07  01 00 00 00  00 06 00 00"
+               // FILEPASS
+               + "2F 00 36 00"
+               + "01 00  01 00  01 00"
+               + "BAADF00D BAADF00D BAADF00D BAADF00D" // docId
+               + "DEADBEEF DEADBEEF DEADBEEF DEADBEEF" // saltData
+               ;
+
+       /**
+        * Hex dump of a sample WINDOW1 record
+        */
+       private static final String SAMPLE_WINDOW1 = "3D 00 12 00"
+               + "00 00 00 00 40 38 55 23 38 00 00 00 00 00 01 00 58 02";
+
+       /**
+        * Makes sure that a default password mismatch condition is represented with {@link EncryptedDocumentException}
+        */
+       public void testDefaultPassword() {
+               // This encodng depends on docId, password and stream position
+               final String SAMPLE_WINDOW1_ENCR1 = "3D 00 12 00"
+                       + "C4, 9B, 02, 50, 86, E0, DF, 34, FB, 57, 0E, 8C, CE, 25, 45, E3, 80, 01";
+
+               byte[] dataWrongDefault = HexRead.readFromString(""
+                               + COMMON_HEX_DATA
+                               + "00000000 00000000 00000000 00000001"
+                               + SAMPLE_WINDOW1_ENCR1
+               );
+
+               RecordFactoryInputStream rfis;
+               try {
+                       rfis = createRFIS(dataWrongDefault);
+                       throw new AssertionFailedError("Expected password mismatch error");
+               } catch (EncryptedDocumentException e) {
+                       // expected during successful test
+                       if (!e.getMessage().equals("Default password is invalid for docId/saltData/saltHash")) {
+                               throw e;
+                       }
+               }
+
+               byte[] dataCorrectDefault = HexRead.readFromString(""
+                               + COMMON_HEX_DATA
+                               + "137BEF04 969A200B 306329DE 52254005" // correct saltHash for default password (and docId/saltHash)
+                               + SAMPLE_WINDOW1_ENCR1
+               );
+
+               rfis = createRFIS(dataCorrectDefault);
+
+               confirmReadInitialRecords(rfis);
+       }
+
+       /**
+        * Makes sure that an incorrect user supplied password condition is represented with {@link EncryptedDocumentException}
+        */
+       public void testSuppliedPassword() {
+               // This encodng depends on docId, password and stream position
+               final String SAMPLE_WINDOW1_ENCR2 = "3D 00 12 00"
+                       + "45, B9, 90, FE, B6, C6, EC, 73, EE, 3F, 52, 45, 97, DB, E3, C1, D6, FE";
+
+               byte[] dataWrongDefault = HexRead.readFromString(""
+                               + COMMON_HEX_DATA
+                               + "00000000 00000000 00000000 00000000"
+                               + SAMPLE_WINDOW1_ENCR2
+               );
+
+
+               Biff8EncryptionKey.setCurrentUserPassword("passw0rd");
+
+               RecordFactoryInputStream rfis;
+               try {
+                       rfis = createRFIS(dataWrongDefault);
+                       throw new AssertionFailedError("Expected password mismatch error");
+               } catch (EncryptedDocumentException e) {
+                       // expected during successful test
+                       if (!e.getMessage().equals("Supplied password is invalid for docId/saltData/saltHash")) {
+                               throw e;
+                       }
+               }
+
+               byte[] dataCorrectDefault = HexRead.readFromString(""
+                               + COMMON_HEX_DATA
+                               + "C728659A C38E35E0 568A338F C3FC9D70" // correct saltHash for supplied password (and docId/saltHash)
+                               + SAMPLE_WINDOW1_ENCR2
+               );
+
+               rfis = createRFIS(dataCorrectDefault);
+               Biff8EncryptionKey.setCurrentUserPassword(null);
+
+               confirmReadInitialRecords(rfis);
+       }
+
+       /**
+        * makes sure the record stream starts with {@link BOFRecord} and then {@link WindowOneRecord}
+        * The second record is gets decrypted so this method also checks its content.
+        */
+       private void confirmReadInitialRecords(RecordFactoryInputStream rfis) {
+               assertEquals(BOFRecord.class, rfis.nextRecord().getClass());
+               WindowOneRecord rec1 = (WindowOneRecord) rfis.nextRecord();
+               assertTrue(Arrays.equals(HexRead.readFromString(SAMPLE_WINDOW1),rec1.serialize()));
+       }
+
+       private static RecordFactoryInputStream createRFIS(byte[] data) {
+               return new RecordFactoryInputStream(new ByteArrayInputStream(data), true);
+       }
+}