mirror of https://github.com/apache/poi.git
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:
parent
f9d10a5631
commit
b23ac7a022
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue