Follow-on for bugzilla 47652 - used more specific exception when password is incorrect

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@804381 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Josh Micich 2009-08-14 21:35:44 +00:00
parent ed16c25a9a
commit 457a13f8a4
4 changed files with 168 additions and 5 deletions

View File

@ -23,6 +23,7 @@ import java.util.List;
import org.apache.poi.hssf.eventusermodel.HSSFEventFactory; import org.apache.poi.hssf.eventusermodel.HSSFEventFactory;
import org.apache.poi.hssf.eventusermodel.HSSFListener; import org.apache.poi.hssf.eventusermodel.HSSFListener;
import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey; import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
import org.apache.poi.EncryptedDocumentException;
/** /**
* A stream based way to get at complete records, with * 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 * Needed for protected files because each byte is encrypted with respect to its absolute
* position from the start of the stream. * position from the start of the stream.
*/ */
public static final class StreamEncryptionInfo { private static final class StreamEncryptionInfo {
private final int _initialRecordsSize; private final int _initialRecordsSize;
private final FilePassRecord _filePassRec; private final FilePassRecord _filePassRec;
private final Record _lastRecord; private final Record _lastRecord;
@ -97,7 +98,9 @@ public final class RecordFactoryInputStream {
key = Biff8EncryptionKey.create(userPassword, fpr.getDocId()); key = Biff8EncryptionKey.create(userPassword, fpr.getDocId());
} }
if (!key.validate(fpr.getSaltData(), fpr.getSaltHash())) { 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); return new RecordInputStream(original, key, _initialRecordsSize);
} }

View File

@ -90,9 +90,21 @@ public final class Biff8EncryptionKey {
md5.update(saltDataPrime); md5.update(saltDataPrime);
byte[] finalSaltResult = md5.digest(); 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); 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) { private static void check16Bytes(byte[] data, String argName) {
if (data.length != 16) { if (data.length != 16) {
throw new IllegalArgumentException("Expected 16 byte " + argName + ", but got " + HexDump.toHex(data)); throw new IllegalArgumentException("Expected 16 byte " + argName + ", but got " + HexDump.toHex(data));

View File

@ -47,9 +47,11 @@ public final class AllRecordTests {
result.addTestSuite(TestBOFRecord.class); result.addTestSuite(TestBOFRecord.class);
result.addTestSuite(TestBoolErrRecord.class); result.addTestSuite(TestBoolErrRecord.class);
result.addTestSuite(TestBoundSheetRecord.class); result.addTestSuite(TestBoundSheetRecord.class);
result.addTestSuite(TestCellRange.class);
result.addTestSuite(TestCFHeaderRecord.class); result.addTestSuite(TestCFHeaderRecord.class);
result.addTestSuite(TestCFRuleRecord.class); result.addTestSuite(TestCFRuleRecord.class);
result.addTestSuite(TestCommonObjectDataSubRecord.class); result.addTestSuite(TestCommonObjectDataSubRecord.class);
result.addTestSuite(TestConstantValueParser.class);
result.addTestSuite(TestDrawingGroupRecord.class); result.addTestSuite(TestDrawingGroupRecord.class);
result.addTestSuite(TestEmbeddedObjectRefSubRecord.class); result.addTestSuite(TestEmbeddedObjectRefSubRecord.class);
result.addTestSuite(TestEndSubRecord.class); result.addTestSuite(TestEndSubRecord.class);
@ -67,8 +69,9 @@ public final class AllRecordTests {
result.addTestSuite(TestObjRecord.class); result.addTestSuite(TestObjRecord.class);
result.addTestSuite(TestPaletteRecord.class); result.addTestSuite(TestPaletteRecord.class);
result.addTestSuite(TestPaneRecord.class); result.addTestSuite(TestPaneRecord.class);
result.addTestSuite(TestRecordInputStream.class);
result.addTestSuite(TestRecordFactory.class); result.addTestSuite(TestRecordFactory.class);
result.addTestSuite(TestRecordFactoryInputStream.class);
result.addTestSuite(TestRecordInputStream.class);
result.addTestSuite(TestSCLRecord.class); result.addTestSuite(TestSCLRecord.class);
result.addTestSuite(TestSSTDeserializer.class); result.addTestSuite(TestSSTDeserializer.class);
result.addTestSuite(TestSSTRecord.class); result.addTestSuite(TestSSTRecord.class);
@ -84,8 +87,6 @@ public final class AllRecordTests {
result.addTestSuite(TestUnicodeNameRecord.class); result.addTestSuite(TestUnicodeNameRecord.class);
result.addTestSuite(TestUnicodeString.class); result.addTestSuite(TestUnicodeString.class);
result.addTestSuite(TestWriteAccessRecord.class); result.addTestSuite(TestWriteAccessRecord.class);
result.addTestSuite(TestCellRange.class);
result.addTestSuite(TestConstantValueParser.class);
return result; return result;
} }
} }

View File

@ -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);
}
}