diff options
author | Andreas Beeker <kiwiwings@apache.org> | 2014-09-04 22:50:28 +0000 |
---|---|---|
committer | Andreas Beeker <kiwiwings@apache.org> | 2014-09-04 22:50:28 +0000 |
commit | d71574d53115d54f2a30b602ea131e8e1671850c (patch) | |
tree | 825dbbb78a1c34348de006770427c928b66ac371 | |
parent | f1a0b0d3159211393314e52decee2d9651ea25d6 (diff) | |
download | poi-d71574d53115d54f2a30b602ea131e8e1671850c.tar.gz poi-d71574d53115d54f2a30b602ea131e8e1671850c.zip |
Bug 51483 - XSSF locking of specific features not working
Added some documentation to the crypto functions and adapted xor1verifier code to the OFFCrypto-Docs
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1622577 13f79535-47bb-0310-9956-ffa450edef68
13 files changed, 692 insertions, 209 deletions
diff --git a/src/java/org/apache/poi/hssf/record/PasswordRecord.java b/src/java/org/apache/poi/hssf/record/PasswordRecord.java index 9baff6f97d..c38a1230ca 100644 --- a/src/java/org/apache/poi/hssf/record/PasswordRecord.java +++ b/src/java/org/apache/poi/hssf/record/PasswordRecord.java @@ -17,6 +17,7 @@ package org.apache.poi.hssf.record; +import org.apache.poi.poifs.crypt.CryptoFunctions; import org.apache.poi.util.HexDump; import org.apache.poi.util.LittleEndianOutput; @@ -39,23 +40,13 @@ public final class PasswordRecord extends StandardRecord { field_1_password = in.readShort(); } - //this is the world's lamest "security". thanks to Wouter van Vugt for making me - //not have to try real hard. -ACO + /** + * Return the password hash + * + * @deprecated use {@link CryptoFunctions#createXorVerifier1(String)} + */ public static short hashPassword(String password) { - byte[] passwordCharacters = password.getBytes(); - int hash = 0; - if (passwordCharacters.length > 0) { - int charIndex = passwordCharacters.length; - while (charIndex-- > 0) { - hash = ((hash >> 14) & 0x01) | ((hash << 1) & 0x7fff); - hash ^= passwordCharacters[charIndex]; - } - // also hash with charcount - hash = ((hash >> 14) & 0x01) | ((hash << 1) & 0x7fff); - hash ^= passwordCharacters.length; - hash ^= (0x8000 | ('N' << 8) | 'K'); - } - return (short)hash; + return (short)CryptoFunctions.createXorVerifier1(password); } /** diff --git a/src/java/org/apache/poi/hssf/record/aggregates/WorksheetProtectionBlock.java b/src/java/org/apache/poi/hssf/record/aggregates/WorksheetProtectionBlock.java index f838485f74..275fbf123a 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/WorksheetProtectionBlock.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/WorksheetProtectionBlock.java @@ -24,6 +24,7 @@ import org.apache.poi.hssf.record.ProtectRecord; import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.RecordFormatException; import org.apache.poi.hssf.record.ScenarioProtectRecord; +import org.apache.poi.poifs.crypt.CryptoFunctions; /** * Groups the sheet protection records for a worksheet. @@ -186,7 +187,7 @@ public final class WorksheetProtectionBlock extends RecordAggregate { ProtectRecord prec = getProtect(); PasswordRecord pass = getPassword(); prec.setProtect(true); - pass.setPassword(PasswordRecord.hashPassword(password)); + pass.setPassword((short)CryptoFunctions.createXorVerifier1(password)); if (_objectProtectRecord == null && shouldProtectObjects) { ObjectProtectRecord rec = createObjectProtect(); rec.setProtect(true); diff --git a/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java b/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java index f9f970ade9..ffb7498f3f 100644 --- a/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java +++ b/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java @@ -180,14 +180,20 @@ public class CryptoFunctions { }
/**
- *
+ * Initialize a new cipher object with the given cipher properties
+ * If the given algorithm is not implemented in the JCE, it will try to load it from the bouncy castle
+ * provider.
*
- * @param key
- * @param chain
- * @param vec
+ * @param key the secrect key
+ * @param cipherAlgorithm the cipher algorithm
+ * @param chain the chaining mode
+ * @param vec the initialization vector (IV), can be null
* @param cipherMode Cipher.DECRYPT_MODE or Cipher.ENCRYPT_MODE
+ * @param padding
* @return the requested cipher
* @throws GeneralSecurityException
+ * @throws EncryptedDocumentException if the initialization failed or if an algorithm was specified,
+ * which depends on a missing bouncy castle provider
*/
public static Cipher getCipher(SecretKey key, CipherAlgorithm cipherAlgorithm, ChainingMode chain, byte[] vec, int cipherMode, String padding) {
int keySizeInBytes = key.getEncoded().length;
@@ -226,10 +232,26 @@ public class CryptoFunctions { }
}
+ /**
+ * Returns a new byte array with a truncated to the given size.
+ * If the hash has less then size bytes, it will be filled with 0x36-bytes
+ *
+ * @param hash the to be truncated/filled hash byte array
+ * @param size the size of the returned byte array
+ * @return the padded hash
+ */
public static byte[] getBlock36(byte[] hash, int size) {
return getBlockX(hash, size, (byte)0x36);
}
+ /**
+ * Returns a new byte array with a truncated to the given size.
+ * If the hash has less then size bytes, it will be filled with 0-bytes
+ *
+ * @param hash the to be truncated/filled hash byte array
+ * @param size the size of the returned byte array
+ * @return the padded hash
+ */
public static byte[] getBlock0(byte[] hash, int size) {
return getBlockX(hash, size, (byte)0);
}
@@ -331,11 +353,11 @@ public class CryptoFunctions { byte[] generatedKey = new byte[4];
//Maximum length of the password is 15 chars.
- final int intMaxPasswordLength = 15;
+ final int maxPasswordLength = 15;
if (!"".equals(password)) {
// Truncate the password to 15 characters
- password = password.substring(0, Math.min(password.length(), intMaxPasswordLength));
+ password = password.substring(0, Math.min(password.length(), maxPasswordLength));
// Construct a new NULL-terminated string consisting of single-byte characters:
// -- > Get the single-byte values by iterating through the Unicode characters of the truncated Password.
@@ -359,7 +381,7 @@ public class CryptoFunctions { // the most significant, if the bit is set, XOR the keys high-order word with the corresponding word from
// the Encryption Matrix
for (int i = 0; i < arrByteChars.length; i++) {
- int tmp = intMaxPasswordLength - arrByteChars.length + i;
+ int tmp = maxPasswordLength - arrByteChars.length + i;
for (int intBit = 0; intBit < 7; intBit++) {
if ((arrByteChars[i] & (0x0001 << intBit)) != 0) {
highOrderWord ^= EncryptionMatrix[tmp][intBit];
@@ -369,22 +391,28 @@ public class CryptoFunctions { // Compute the low-order word of the new key:
- // Initialize with 0
- int lowOrderWord = 0;
+ // SET Verifier TO 0x0000
+ short verifier = 0;
- // For each character in the password, going backwards
- for (int i = arrByteChars.length - 1; i >= 0; i--) {
- // low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR character
- lowOrderWord = (((lowOrderWord >> 14) & 0x0001) | ((lowOrderWord << 1) & 0x7FFF)) ^ arrByteChars[i];
+ // FOR EACH PasswordByte IN PasswordArray IN REVERSE ORDER
+ for (int i = arrByteChars.length-1; i >= 0; i--) {
+ // SET Verifier TO Intermediate3 BITWISE XOR PasswordByte
+ verifier = rotateLeftBase15Bit(verifier);
+ verifier ^= arrByteChars[i];
}
- // Lastly,low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR password length XOR 0xCE4B.
- lowOrderWord = (((lowOrderWord >> 14) & 0x0001) | ((lowOrderWord << 1) & 0x7FFF)) ^ arrByteChars.length ^ 0xCE4B;
+ // as we haven't prepended the password length into the input array
+ // we need to do it now separately ...
+ verifier = rotateLeftBase15Bit(verifier);
+ verifier ^= arrByteChars.length;
+
+ // RETURN Verifier BITWISE XOR 0xCE4B
+ verifier ^= 0xCE4B; // (0x8000 | ('N' << 8) | 'K')
// The byte order of the result shall be reversed [password "Example": 0x64CEED7E becomes 7EEDCE64],
// and that value shall be hashed as defined by the attribute values.
- LittleEndian.putShort(generatedKey, 0, (short)lowOrderWord);
+ LittleEndian.putShort(generatedKey, 0, verifier);
LittleEndian.putShort(generatedKey, 2, (short)highOrderWord);
}
@@ -421,7 +449,7 @@ public class CryptoFunctions { * @see <a href="http://msdn.microsoft.com/en-us/library/dd905229.aspx">2.3.7.4 Binary Document Password Verifier Derivation Method 2</a>
*
* @param password the password
- * @return the verifier
+ * @return the verifier (actually a short value)
*/
public static int createXorVerifier1(String password) {
// the verifier for method 1 is part of the verifier for method 2
@@ -480,4 +508,25 @@ public class CryptoFunctions { private static byte rotateLeft(byte bits, int shift) {
return (byte)(((bits & 0xff) << shift) | ((bits & 0xff) >>> (8 - shift)));
}
+
+ private static short rotateLeftBase15Bit(short verifier) {
+ /*
+ * IF (Verifier BITWISE AND 0x4000) is 0x0000
+ * SET Intermediate1 TO 0
+ * ELSE
+ * SET Intermediate1 TO 1
+ * ENDIF
+ */
+ short intermediate1 = (short)(((verifier & 0x4000) == 0) ? 0 : 1);
+ /*
+ * SET Intermediate2 TO Verifier MULTIPLED BY 2
+ * SET most significant bit of Intermediate2 TO 0
+ */
+ short intermediate2 = (short)((verifier<<1) & 0x7FFF);
+ /*
+ * SET Intermediate3 TO Intermediate1 BITWISE OR Intermediate2
+ */
+ short intermediate3 = (short)(intermediate1 | intermediate2);
+ return intermediate3;
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java b/src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java index 51217184ba..61608822f8 100644 --- a/src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java +++ b/src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java @@ -64,4 +64,11 @@ public enum HashAlgorithm { }
throw new EncryptedDocumentException("hash algorithm not found");
}
+
+ public static HashAlgorithm fromString(String string) {
+ for (HashAlgorithm ha : values()) {
+ if (ha.ecmaString.equalsIgnoreCase(string) || ha.jceId.equalsIgnoreCase(string)) return ha;
+ }
+ throw new EncryptedDocumentException("hash algorithm not found");
+ }
}
\ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java index 60ecf7bb9f..9da8a6955a 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java @@ -17,6 +17,9 @@ package org.apache.poi.xssf.usermodel; +import static org.apache.poi.xssf.usermodel.helpers.XSSFPaswordHelper.setPassword; +import static org.apache.poi.xssf.usermodel.helpers.XSSFPaswordHelper.validatePassword; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -33,7 +36,6 @@ import javax.xml.namespace.QName; import org.apache.poi.POIXMLDocumentPart; import org.apache.poi.POIXMLException; -import org.apache.poi.hssf.record.PasswordRecord; import org.apache.poi.hssf.util.PaneInformation; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.exceptions.PartAlreadyExistsException; @@ -41,6 +43,7 @@ import org.apache.poi.openxml4j.opc.PackagePart; import org.apache.poi.openxml4j.opc.PackageRelationship; import org.apache.poi.openxml4j.opc.PackageRelationshipCollection; import org.apache.poi.openxml4j.opc.TargetMode; +import org.apache.poi.poifs.crypt.HashAlgorithm; import org.apache.poi.ss.SpreadsheetVersion; import org.apache.poi.ss.formula.FormulaShifter; import org.apache.poi.ss.formula.SheetNameFormatter; @@ -52,7 +55,6 @@ import org.apache.poi.ss.util.CellReference; import org.apache.poi.ss.util.SSCellRange; import org.apache.poi.ss.util.SheetUtil; import org.apache.poi.util.Beta; -import org.apache.poi.util.HexDump; import org.apache.poi.util.Internal; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; @@ -1056,7 +1058,7 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet { */ @Override public boolean getProtect() { - return worksheet.isSetSheetProtection() && sheetProtectionEnabled(); + return isSheetLocked(); } /** @@ -1068,10 +1070,9 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet { */ @Override public void protectSheet(String password) { - - if(password != null) { - CTSheetProtection sheetProtection = worksheet.addNewSheetProtection(); - sheetProtection.xsetPassword(stringToExcelPassword(password)); + if (password != null) { + CTSheetProtection sheetProtection = safeGetProtectionField(); + setSheetPassword(password, null); // defaults to xor password sheetProtection.setSheet(true); sheetProtection.setScenarios(true); sheetProtection.setObjects(true); @@ -1081,19 +1082,28 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet { } /** - * Converts a String to a {@link STUnsignedShortHex} value that contains the {@link PasswordRecord#hashPassword(String)} - * value in hexadecimal format - * - * @param password the password string you wish convert to an {@link STUnsignedShortHex} - * @return {@link STUnsignedShortHex} that contains Excel hashed password in Hex format + * Sets the sheet password. + * + * @param password if null, the password will be removed + * @param hashAlgo if null, the password will be set as XOR password (Excel 2010 and earlier) + * otherwise the given algorithm is used for calculating the hash password (Excel 2013) */ - private STUnsignedShortHex stringToExcelPassword(String password) { - STUnsignedShortHex hexPassword = STUnsignedShortHex.Factory.newInstance(); - hexPassword.setStringValue(String.valueOf(HexDump.shortToHex(PasswordRecord.hashPassword(password))).substring(2)); - return hexPassword; + public void setSheetPassword(String password, HashAlgorithm hashAlgo) { + if (password == null && !isSheetProtectionEnabled()) return; + setPassword(safeGetProtectionField(), password, hashAlgo, null); } /** + * Validate the password against the stored hash, the hashing method will be determined + * by the existing password attributes + * @return true, if the hashes match (... though original password may differ ...) + */ + public boolean validateSheetPassword(String password) { + if (!isSheetProtectionEnabled()) return (password == null); + return validatePassword(safeGetProtectionField(), password, null); + } + + /** * Returns the logical row ( 0-based). If you ask for a row that is not * defined you get a null. This is to say row 4 represents the fifth row on a sheet. * @@ -1546,7 +1556,7 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet { worksheet.unsetMergeCells(); } } - + /** * Removes a number of merged regions of cells (hence letting them free) * @@ -2910,304 +2920,440 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet { * @return true when Autofilters are locked and the sheet is protected. */ public boolean isAutoFilterLocked() { - createProtectionFieldIfNotPresent(); - return sheetProtectionEnabled() && worksheet.getSheetProtection().getAutoFilter(); + if (isSheetLocked()) { + return safeGetProtectionField().getAutoFilter(); + } + return false; } /** * @return true when Deleting columns is locked and the sheet is protected. */ public boolean isDeleteColumnsLocked() { - createProtectionFieldIfNotPresent(); - return sheetProtectionEnabled() && worksheet.getSheetProtection().getDeleteColumns(); + if (isSheetLocked()) { + return safeGetProtectionField().getDeleteColumns(); + } + return false; } /** * @return true when Deleting rows is locked and the sheet is protected. */ public boolean isDeleteRowsLocked() { - createProtectionFieldIfNotPresent(); - return sheetProtectionEnabled() && worksheet.getSheetProtection().getDeleteRows(); + if (isSheetLocked()) { + return safeGetProtectionField().getDeleteRows(); + } + return false; } /** * @return true when Formatting cells is locked and the sheet is protected. */ public boolean isFormatCellsLocked() { - createProtectionFieldIfNotPresent(); - return sheetProtectionEnabled() && worksheet.getSheetProtection().getFormatCells(); + if (isSheetLocked()) { + return safeGetProtectionField().getFormatCells(); + } + return false; } /** * @return true when Formatting columns is locked and the sheet is protected. */ public boolean isFormatColumnsLocked() { - createProtectionFieldIfNotPresent(); - return sheetProtectionEnabled() && worksheet.getSheetProtection().getFormatColumns(); + if (isSheetLocked()) { + return safeGetProtectionField().getFormatColumns(); + } + return false; } /** * @return true when Formatting rows is locked and the sheet is protected. */ public boolean isFormatRowsLocked() { - createProtectionFieldIfNotPresent(); - return sheetProtectionEnabled() && worksheet.getSheetProtection().getFormatRows(); + if (isSheetLocked()) { + return safeGetProtectionField().getFormatRows(); + } + return false; } /** * @return true when Inserting columns is locked and the sheet is protected. */ public boolean isInsertColumnsLocked() { - createProtectionFieldIfNotPresent(); - return sheetProtectionEnabled() && worksheet.getSheetProtection().getInsertColumns(); + if (isSheetLocked()) { + return safeGetProtectionField().getInsertColumns(); + } + return false; } /** * @return true when Inserting hyperlinks is locked and the sheet is protected. */ public boolean isInsertHyperlinksLocked() { - createProtectionFieldIfNotPresent(); - return sheetProtectionEnabled() && worksheet.getSheetProtection().getInsertHyperlinks(); + if (isSheetLocked()) { + return safeGetProtectionField().getInsertHyperlinks(); + } + return false; } /** * @return true when Inserting rows is locked and the sheet is protected. */ public boolean isInsertRowsLocked() { - createProtectionFieldIfNotPresent(); - return sheetProtectionEnabled() && worksheet.getSheetProtection().getInsertRows(); + if (isSheetLocked()) { + return safeGetProtectionField().getInsertRows(); + } + return false; } /** * @return true when Pivot tables are locked and the sheet is protected. */ public boolean isPivotTablesLocked() { - createProtectionFieldIfNotPresent(); - return sheetProtectionEnabled() && worksheet.getSheetProtection().getPivotTables(); + if (isSheetLocked()) { + return safeGetProtectionField().getPivotTables(); + } + return false; } /** * @return true when Sorting is locked and the sheet is protected. */ public boolean isSortLocked() { - createProtectionFieldIfNotPresent(); - return sheetProtectionEnabled() && worksheet.getSheetProtection().getSort(); + if (isSheetLocked()) { + return safeGetProtectionField().getSort(); + } + return false; } /** * @return true when Objects are locked and the sheet is protected. */ public boolean isObjectsLocked() { - createProtectionFieldIfNotPresent(); - return sheetProtectionEnabled() && worksheet.getSheetProtection().getObjects(); + if (isSheetLocked()) { + return safeGetProtectionField().getObjects(); + } + return false; } /** * @return true when Scenarios are locked and the sheet is protected. */ public boolean isScenariosLocked() { - createProtectionFieldIfNotPresent(); - return sheetProtectionEnabled() && worksheet.getSheetProtection().getScenarios(); + if (isSheetLocked()) { + return safeGetProtectionField().getScenarios(); + } + return false; } /** * @return true when Selection of locked cells is locked and the sheet is protected. */ public boolean isSelectLockedCellsLocked() { - createProtectionFieldIfNotPresent(); - return sheetProtectionEnabled() && worksheet.getSheetProtection().getSelectLockedCells(); + if (isSheetLocked()) { + return safeGetProtectionField().getSelectLockedCells(); + } + return false; } /** * @return true when Selection of unlocked cells is locked and the sheet is protected. */ public boolean isSelectUnlockedCellsLocked() { - createProtectionFieldIfNotPresent(); - return sheetProtectionEnabled() && worksheet.getSheetProtection().getSelectUnlockedCells(); + if (isSheetLocked()) { + return safeGetProtectionField().getSelectUnlockedCells(); + } + return false; } /** * @return true when Sheet is Protected. */ public boolean isSheetLocked() { - createProtectionFieldIfNotPresent(); - return sheetProtectionEnabled() && worksheet.getSheetProtection().getSheet(); + if (worksheet.isSetSheetProtection()) { + return safeGetProtectionField().getSheet(); + } + return false; } /** * Enable sheet protection */ public void enableLocking() { - createProtectionFieldIfNotPresent(); - worksheet.getSheetProtection().setSheet(true); + safeGetProtectionField().setSheet(true); } /** * Disable sheet protection */ public void disableLocking() { - createProtectionFieldIfNotPresent(); - worksheet.getSheetProtection().setSheet(false); + safeGetProtectionField().setSheet(false); } /** * Enable Autofilters locking. - * This does not modify sheet protection status. - * To enforce this locking, call {@link #enableLocking()} + * @deprecated use {@link #lockAutoFilter(boolean)} */ public void lockAutoFilter() { - createProtectionFieldIfNotPresent(); - worksheet.getSheetProtection().setAutoFilter(true); + lockAutoFilter(true); } /** - * Enable Deleting columns locking. + * Enable or disable Autofilters locking. * This does not modify sheet protection status. - * To enforce this locking, call {@link #enableLocking()} + * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} + */ + public void lockAutoFilter(boolean enabled) { + safeGetProtectionField().setAutoFilter(enabled); + } + + /** + * Enable Deleting columns locking. + * @deprecated use {@link #lockDeleteColumns(boolean)} */ public void lockDeleteColumns() { - createProtectionFieldIfNotPresent(); - worksheet.getSheetProtection().setDeleteColumns(true); + lockDeleteColumns(true); } /** - * Enable Deleting rows locking. + * Enable or disable Deleting columns locking. * This does not modify sheet protection status. - * To enforce this locking, call {@link #enableLocking()} + * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} + */ + public void lockDeleteColumns(boolean enabled) { + safeGetProtectionField().setDeleteColumns(enabled); + } + + /** + * Enable Deleting rows locking. + * @deprecated use {@link #lockDeleteRows(boolean)} */ public void lockDeleteRows() { - createProtectionFieldIfNotPresent(); - worksheet.getSheetProtection().setDeleteRows(true); + lockDeleteRows(true); } /** - * Enable Formatting cells locking. + * Enable or disable Deleting rows locking. * This does not modify sheet protection status. - * To enforce this locking, call {@link #enableLocking()} + * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} + */ + public void lockDeleteRows(boolean enabled) { + safeGetProtectionField().setDeleteRows(enabled); + } + + /** + * Enable Formatting cells locking. + * @deprecated use {@link #lockFormatCells(boolean)} */ public void lockFormatCells() { - createProtectionFieldIfNotPresent(); - worksheet.getSheetProtection().setDeleteColumns(true); + lockFormatCells(true); } /** - * Enable Formatting columns locking. + * Enable or disable Formatting cells locking. * This does not modify sheet protection status. - * To enforce this locking, call {@link #enableLocking()} + * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} + */ + public void lockFormatCells(boolean enabled) { + safeGetProtectionField().setFormatCells(enabled); + } + + /** + * Enable Formatting columns locking. + * @deprecated use {@link #lockFormatColumns(boolean)} */ public void lockFormatColumns() { - createProtectionFieldIfNotPresent(); - worksheet.getSheetProtection().setFormatColumns(true); + lockFormatColumns(true); } /** - * Enable Formatting rows locking. + * Enable or disable Formatting columns locking. * This does not modify sheet protection status. - * To enforce this locking, call {@link #enableLocking()} + * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} + */ + public void lockFormatColumns(boolean enabled) { + safeGetProtectionField().setFormatColumns(enabled); + } + + /** + * Enable Formatting rows locking. + * @deprecated use {@link #lockFormatRows(boolean)} */ public void lockFormatRows() { - createProtectionFieldIfNotPresent(); - worksheet.getSheetProtection().setFormatRows(true); + lockFormatRows(true); } /** - * Enable Inserting columns locking. + * Enable or disable Formatting rows locking. * This does not modify sheet protection status. - * To enforce this locking, call {@link #enableLocking()} + * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} + */ + public void lockFormatRows(boolean enabled) { + safeGetProtectionField().setFormatRows(enabled); + } + + /** + * Enable Inserting columns locking. + * @deprecated use {@link #lockInsertColumns(boolean)} */ public void lockInsertColumns() { - createProtectionFieldIfNotPresent(); - worksheet.getSheetProtection().setInsertColumns(true); + lockInsertColumns(true); } /** - * Enable Inserting hyperlinks locking. + * Enable or disable Inserting columns locking. * This does not modify sheet protection status. - * To enforce this locking, call {@link #enableLocking()} + * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} + */ + public void lockInsertColumns(boolean enabled) { + safeGetProtectionField().setInsertColumns(enabled); + } + + /** + * Enable Inserting hyperlinks locking. + * @deprecated use {@link #lockInsertHyperlinks(boolean)} */ public void lockInsertHyperlinks() { - createProtectionFieldIfNotPresent(); - worksheet.getSheetProtection().setInsertHyperlinks(true); + lockInsertHyperlinks(true); } /** - * Enable Inserting rows locking. + * Enable or disable Inserting hyperlinks locking. * This does not modify sheet protection status. - * To enforce this locking, call {@link #enableLocking()} + * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} + */ + public void lockInsertHyperlinks(boolean enabled) { + safeGetProtectionField().setInsertHyperlinks(enabled); + } + + /** + * Enable Inserting rows locking. + * @deprecated use {@link #lockInsertRows(boolean)} */ public void lockInsertRows() { - createProtectionFieldIfNotPresent(); - worksheet.getSheetProtection().setInsertRows(true); + lockInsertRows(true); } /** - * Enable Pivot Tables locking. + * Enable or disable Inserting rows locking. * This does not modify sheet protection status. - * To enforce this locking, call {@link #enableLocking()} + * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} + */ + public void lockInsertRows(boolean enabled) { + safeGetProtectionField().setInsertRows(enabled); + } + + /** + * Enable Pivot Tables locking. + * @deprecated use {@link #lockPivotTables(boolean)} */ public void lockPivotTables() { - createProtectionFieldIfNotPresent(); - worksheet.getSheetProtection().setPivotTables(true); + lockPivotTables(true); } /** - * Enable Sort locking. + * Enable or disable Pivot Tables locking. * This does not modify sheet protection status. - * To enforce this locking, call {@link #enableLocking()} + * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} + */ + public void lockPivotTables(boolean enabled) { + safeGetProtectionField().setPivotTables(enabled); + } + + /** + * Enable Sort locking. + * @deprecated use {@link #lockSort(boolean)} */ public void lockSort() { - createProtectionFieldIfNotPresent(); - worksheet.getSheetProtection().setSort(true); + lockSort(true); } /** - * Enable Objects locking. + * Enable or disable Sort locking. * This does not modify sheet protection status. - * To enforce this locking, call {@link #enableLocking()} + * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} + */ + public void lockSort(boolean enabled) { + safeGetProtectionField().setSort(enabled); + } + + /** + * Enable Objects locking. + * @deprecated use {@link #lockObjects(boolean)} */ public void lockObjects() { - createProtectionFieldIfNotPresent(); - worksheet.getSheetProtection().setObjects(true); + lockObjects(true); } /** - * Enable Scenarios locking. + * Enable or disable Objects locking. * This does not modify sheet protection status. - * To enforce this locking, call {@link #enableLocking()} + * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} + */ + public void lockObjects(boolean enabled) { + safeGetProtectionField().setObjects(enabled); + } + + /** + * Enable Scenarios locking. + * @deprecated use {@link #lockScenarios(boolean)} */ public void lockScenarios() { - createProtectionFieldIfNotPresent(); - worksheet.getSheetProtection().setScenarios(true); + lockScenarios(true); } /** - * Enable Selection of locked cells locking. + * Enable or disable Scenarios locking. * This does not modify sheet protection status. - * To enforce this locking, call {@link #enableLocking()} + * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} + */ + public void lockScenarios(boolean enabled) { + safeGetProtectionField().setScenarios(enabled); + } + + /** + * Enable Selection of locked cells locking. + * @deprecated use {@link #lockSelectLockedCells(boolean)} */ public void lockSelectLockedCells() { - createProtectionFieldIfNotPresent(); - worksheet.getSheetProtection().setSelectLockedCells(true); + lockSelectLockedCells(true); } /** - * Enable Selection of unlocked cells locking. + * Enable or disable Selection of locked cells locking. * This does not modify sheet protection status. - * To enforce this locking, call {@link #enableLocking()} + * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} + */ + public void lockSelectLockedCells(boolean enabled) { + safeGetProtectionField().setSelectLockedCells(enabled); + } + + /** + * Enable Selection of unlocked cells locking. + * @deprecated use {@link #lockSelectUnlockedCells(boolean)} */ public void lockSelectUnlockedCells() { - createProtectionFieldIfNotPresent(); - worksheet.getSheetProtection().setSelectUnlockedCells(true); + lockSelectUnlockedCells(true); + } + + /** + * Enable or disable Selection of unlocked cells locking. + * This does not modify sheet protection status. + * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} + */ + public void lockSelectUnlockedCells(boolean enabled) { + safeGetProtectionField().setSelectUnlockedCells(enabled); } - private void createProtectionFieldIfNotPresent() { - if (worksheet.getSheetProtection() == null) { - worksheet.setSheetProtection(CTSheetProtection.Factory.newInstance()); + private CTSheetProtection safeGetProtectionField() { + if (!isSheetProtectionEnabled()) { + return worksheet.addNewSheetProtection(); } + return worksheet.getSheetProtection(); } - private boolean sheetProtectionEnabled() { - return worksheet.getSheetProtection().getSheet(); + /* package */ boolean isSheetProtectionEnabled() { + return (worksheet.isSetSheetProtection()); } /* package */ boolean isCellInArrayFormulaContext(XSSFCell cell) { diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java index f5dc36b336..8beb39aa98 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java @@ -17,6 +17,9 @@ package org.apache.poi.xssf.usermodel; +import static org.apache.poi.xssf.usermodel.helpers.XSSFPaswordHelper.setPassword; +import static org.apache.poi.xssf.usermodel.helpers.XSSFPaswordHelper.validatePassword; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -47,6 +50,7 @@ import org.apache.poi.openxml4j.opc.PackageRelationship; import org.apache.poi.openxml4j.opc.PackageRelationshipTypes; import org.apache.poi.openxml4j.opc.PackagingURIHelper; import org.apache.poi.openxml4j.opc.TargetMode; +import org.apache.poi.poifs.crypt.HashAlgorithm; import org.apache.poi.ss.formula.SheetNameFormatter; import org.apache.poi.ss.formula.udf.IndexedUDFFinder; import org.apache.poi.ss.formula.udf.UDFFinder; @@ -1736,60 +1740,108 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Iterable<X * Locks the structure of workbook. */ public void lockStructure() { - createProtectionFieldIfNotPresent(); - workbook.getWorkbookProtection().setLockStructure(true); + safeGetWorkbookProtection().setLockStructure(true); } /** * Unlocks the structure of workbook. */ public void unLockStructure() { - createProtectionFieldIfNotPresent(); - workbook.getWorkbookProtection().setLockStructure(false); + safeGetWorkbookProtection().setLockStructure(false); } /** * Locks the windows that comprise the workbook. */ public void lockWindows() { - createProtectionFieldIfNotPresent(); - workbook.getWorkbookProtection().setLockWindows(true); + safeGetWorkbookProtection().setLockWindows(true); } /** * Unlocks the windows that comprise the workbook. */ public void unLockWindows() { - createProtectionFieldIfNotPresent(); - workbook.getWorkbookProtection().setLockWindows(false); + safeGetWorkbookProtection().setLockWindows(false); } /** * Locks the workbook for revisions. */ public void lockRevision() { - createProtectionFieldIfNotPresent(); - workbook.getWorkbookProtection().setLockRevision(true); + safeGetWorkbookProtection().setLockRevision(true); } /** * Unlocks the workbook for revisions. */ public void unLockRevision() { - createProtectionFieldIfNotPresent(); - workbook.getWorkbookProtection().setLockRevision(false); + safeGetWorkbookProtection().setLockRevision(false); } - private boolean workbookProtectionPresent() { - return workbook.getWorkbookProtection() != null; + /** + * Sets the workbook password. + * + * @param password if null, the password will be removed + * @param hashAlgo if null, the password will be set as XOR password (Excel 2010 and earlier) + * otherwise the given algorithm is used for calculating the hash password (Excel 2013) + */ + public void setWorkbookPassword(String password, HashAlgorithm hashAlgo) { + if (password == null && !workbookProtectionPresent()) return; + setPassword(safeGetWorkbookProtection(), password, hashAlgo, "workbook"); } - private void createProtectionFieldIfNotPresent() { - if (workbook.getWorkbookProtection() == null){ - workbook.setWorkbookProtection(CTWorkbookProtection.Factory.newInstance()); - } + /** + * Validate the password against the stored hash, the hashing method will be determined + * by the existing password attributes + * @return true, if the hashes match (... though original password may differ ...) + */ + public boolean validateWorkbookPassword(String password) { + if (!workbookProtectionPresent()) return (password == null); + return validatePassword(safeGetWorkbookProtection(), password, "workbook"); + } + + /** + * Sets the revisions password. + * + * @param password if null, the password will be removed + * @param hashAlgo if null, the password will be set as XOR password (Excel 2010 and earlier) + * otherwise the given algorithm is used for calculating the hash password (Excel 2013) + */ + public void setRevisionsPassword(String password, HashAlgorithm hashAlgo) { + if (password == null && !workbookProtectionPresent()) return; + setPassword(safeGetWorkbookProtection(), password, hashAlgo, "revisions"); + } + + /** + * Validate the password against the stored hash, the hashing method will be determined + * by the existing password attributes + * @return true if the hashes match (... though original password may differ ...) + */ + public boolean validateRevisionsPassword(String password) { + if (!workbookProtectionPresent()) return (password == null); + return validatePassword(safeGetWorkbookProtection(), password, "revisions"); + } + + /** + * Removes the workbook protection settings + */ + public void unLock() { + if (workbookProtectionPresent()) { + workbook.unsetWorkbookProtection(); + } + } + + private boolean workbookProtectionPresent() { + return workbook.isSetWorkbookProtection(); } + private CTWorkbookProtection safeGetWorkbookProtection() { + if (!workbookProtectionPresent()){ + return workbook.addNewWorkbookProtection(); + } + return workbook.getWorkbookProtection(); + } + /** * * Returns the locator of user-defined functions. diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFPaswordHelper.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFPaswordHelper.java new file mode 100644 index 0000000000..c887129b69 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFPaswordHelper.java @@ -0,0 +1,128 @@ +/*
+ * ====================================================================
+ * 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.xssf.usermodel.helpers;
+
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+import javax.xml.bind.DatatypeConverter;
+import javax.xml.namespace.QName;
+
+import org.apache.poi.poifs.crypt.CryptoFunctions;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.xmlbeans.XmlCursor;
+import org.apache.xmlbeans.XmlObject;
+
+public class XSSFPaswordHelper {
+ /**
+ * Sets the XORed or hashed password
+ *
+ * @param xobj the xmlbeans object which contains the password attributes
+ * @param password the password, if null, the password attributes will be removed
+ * @param hashAlgo the hash algorithm, if null the password will be XORed
+ * @param prefix the prefix of the password attributes, may be null
+ */
+ public static void setPassword(XmlObject xobj, String password, HashAlgorithm hashAlgo, String prefix) {
+ XmlCursor cur = xobj.newCursor();
+
+ if (password == null) {
+ cur.removeAttribute(getAttrName(prefix, "password"));
+ cur.removeAttribute(getAttrName(prefix, "algorithmName"));
+ cur.removeAttribute(getAttrName(prefix, "hashValue"));
+ cur.removeAttribute(getAttrName(prefix, "saltValue"));
+ cur.removeAttribute(getAttrName(prefix, "spinCount"));
+ return;
+ }
+
+ cur.toFirstContentToken();
+ if (hashAlgo == null) {
+ int hash = CryptoFunctions.createXorVerifier1(password);
+ cur.insertAttributeWithValue(getAttrName(prefix, "password"), Integer.toHexString(hash).toUpperCase());
+ } else {
+ SecureRandom random = new SecureRandom();
+ byte salt[] = random.generateSeed(16);
+
+ // Iterations specifies the number of times the hashing function shall be iteratively run (using each
+ // iteration's result as the input for the next iteration).
+ int spinCount = 100000;
+
+ // Implementation Notes List:
+ // --> In this third stage, the reversed byte order legacy hash from the second stage shall
+ // be converted to Unicode hex string representation
+ byte hash[] = CryptoFunctions.hashPassword(password, hashAlgo, salt, spinCount, false);
+
+ cur.insertAttributeWithValue(getAttrName(prefix, "algorithmName"), hashAlgo.jceId);
+ cur.insertAttributeWithValue(getAttrName(prefix, "hashValue"), DatatypeConverter.printBase64Binary(hash));
+ cur.insertAttributeWithValue(getAttrName(prefix, "saltValue"), DatatypeConverter.printBase64Binary(salt));
+ cur.insertAttributeWithValue(getAttrName(prefix, "spinCount"), ""+spinCount);
+ }
+ cur.dispose();
+ }
+
+ /**
+ * Validates the password, i.e.
+ * calculates the hash of the given password and compares it against the stored hash
+ *
+ * @param xobj the xmlbeans object which contains the password attributes
+ * @param password the password, if null the method will always return false,
+ * even if there's no password set
+ * @param prefix the prefix of the password attributes, may be null
+ *
+ * @return true, if the hashes match
+ */
+ public static boolean validatePassword(XmlObject xobj, String password, String prefix) {
+ // TODO: is "velvetSweatshop" the default password?
+ if (password == null) return false;
+
+ XmlCursor cur = xobj.newCursor();
+ String xorHashVal = cur.getAttributeText(getAttrName(prefix, "password"));
+ String algoName = cur.getAttributeText(getAttrName(prefix, "algorithmName"));
+ String hashVal = cur.getAttributeText(getAttrName(prefix, "hashValue"));
+ String saltVal = cur.getAttributeText(getAttrName(prefix, "saltValue"));
+ String spinCount = cur.getAttributeText(getAttrName(prefix, "spinCount"));
+ cur.dispose();
+
+ if (xorHashVal != null) {
+ int hash1 = Integer.parseInt(xorHashVal, 16);
+ int hash2 = CryptoFunctions.createXorVerifier1(password);
+ return hash1 == hash2;
+ } else {
+ if (hashVal == null || algoName == null || saltVal == null || spinCount == null) {
+ return false;
+ }
+
+ byte hash1[] = DatatypeConverter.parseBase64Binary(hashVal);
+ HashAlgorithm hashAlgo = HashAlgorithm.fromString(algoName);
+ byte salt[] = DatatypeConverter.parseBase64Binary(saltVal);
+ int spinCnt = Integer.parseInt(spinCount);
+ byte hash2[] = CryptoFunctions.hashPassword(password, hashAlgo, salt, spinCnt, false);
+ return Arrays.equals(hash1, hash2);
+ }
+ }
+
+
+ private static QName getAttrName(String prefix, String name) {
+ if (prefix == null || "".equals(prefix)) {
+ return new QName(name);
+ } else {
+ return new QName(prefix+Character.toUpperCase(name.charAt(0))+name.substring(1));
+ }
+ }
+}
diff --git a/src/ooxml/testcases/org/apache/poi/xssf/TestSheetProtection.java b/src/ooxml/testcases/org/apache/poi/xssf/TestSheetProtection.java index 156c0dec49..f90804fa4c 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/TestSheetProtection.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/TestSheetProtection.java @@ -16,12 +16,11 @@ ==================================================================== */ package org.apache.poi.xssf; -import org.apache.poi.xssf.usermodel.XSSFSheet; +import junit.framework.TestCase; +import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; -import junit.framework.TestCase; - public class TestSheetProtection extends TestCase { private XSSFSheet sheet; @@ -75,6 +74,8 @@ public class TestSheetProtection extends TestCase { assertFalse(sheet.isAutoFilterLocked()); sheet.enableLocking(); assertTrue(sheet.isAutoFilterLocked()); + sheet.lockAutoFilter(false); + assertFalse(sheet.isAutoFilterLocked()); } public void testWriteDeleteColumns() throws Exception { @@ -83,6 +84,8 @@ public class TestSheetProtection extends TestCase { assertFalse(sheet.isDeleteColumnsLocked()); sheet.enableLocking(); assertTrue(sheet.isDeleteColumnsLocked()); + sheet.lockDeleteColumns(false); + assertFalse(sheet.isDeleteColumnsLocked()); } public void testWriteDeleteRows() throws Exception { @@ -91,6 +94,8 @@ public class TestSheetProtection extends TestCase { assertFalse(sheet.isDeleteRowsLocked()); sheet.enableLocking(); assertTrue(sheet.isDeleteRowsLocked()); + sheet.lockDeleteRows(false); + assertFalse(sheet.isDeleteRowsLocked()); } public void testWriteFormatCells() throws Exception { @@ -99,6 +104,8 @@ public class TestSheetProtection extends TestCase { assertFalse(sheet.isFormatCellsLocked()); sheet.enableLocking(); assertTrue(sheet.isFormatCellsLocked()); + sheet.lockFormatCells(false); + assertFalse(sheet.isFormatCellsLocked()); } public void testWriteFormatColumns() throws Exception { @@ -107,6 +114,8 @@ public class TestSheetProtection extends TestCase { assertFalse(sheet.isFormatColumnsLocked()); sheet.enableLocking(); assertTrue(sheet.isFormatColumnsLocked()); + sheet.lockFormatColumns(false); + assertFalse(sheet.isFormatColumnsLocked()); } public void testWriteFormatRows() throws Exception { @@ -115,6 +124,8 @@ public class TestSheetProtection extends TestCase { assertFalse(sheet.isFormatRowsLocked()); sheet.enableLocking(); assertTrue(sheet.isFormatRowsLocked()); + sheet.lockFormatRows(false); + assertFalse(sheet.isFormatRowsLocked()); } public void testWriteInsertColumns() throws Exception { @@ -123,6 +134,8 @@ public class TestSheetProtection extends TestCase { assertFalse(sheet.isInsertColumnsLocked()); sheet.enableLocking(); assertTrue(sheet.isInsertColumnsLocked()); + sheet.lockInsertColumns(false); + assertFalse(sheet.isInsertColumnsLocked()); } public void testWriteInsertHyperlinks() throws Exception { @@ -131,6 +144,8 @@ public class TestSheetProtection extends TestCase { assertFalse(sheet.isInsertHyperlinksLocked()); sheet.enableLocking(); assertTrue(sheet.isInsertHyperlinksLocked()); + sheet.lockInsertHyperlinks(false); + assertFalse(sheet.isInsertHyperlinksLocked()); } public void testWriteInsertRows() throws Exception { @@ -139,6 +154,8 @@ public class TestSheetProtection extends TestCase { assertFalse(sheet.isInsertRowsLocked()); sheet.enableLocking(); assertTrue(sheet.isInsertRowsLocked()); + sheet.lockInsertRows(false); + assertFalse(sheet.isInsertRowsLocked()); } public void testWritePivotTables() throws Exception { @@ -147,6 +164,8 @@ public class TestSheetProtection extends TestCase { assertFalse(sheet.isPivotTablesLocked()); sheet.enableLocking(); assertTrue(sheet.isPivotTablesLocked()); + sheet.lockPivotTables(false); + assertFalse(sheet.isPivotTablesLocked()); } public void testWriteSort() throws Exception { @@ -155,6 +174,8 @@ public class TestSheetProtection extends TestCase { assertFalse(sheet.isSortLocked()); sheet.enableLocking(); assertTrue(sheet.isSortLocked()); + sheet.lockSort(false); + assertFalse(sheet.isSortLocked()); } public void testWriteObjects() throws Exception { @@ -163,6 +184,8 @@ public class TestSheetProtection extends TestCase { assertFalse(sheet.isObjectsLocked()); sheet.enableLocking(); assertTrue(sheet.isObjectsLocked()); + sheet.lockObjects(false); + assertFalse(sheet.isObjectsLocked()); } public void testWriteScenarios() throws Exception { @@ -171,6 +194,8 @@ public class TestSheetProtection extends TestCase { assertFalse(sheet.isScenariosLocked()); sheet.enableLocking(); assertTrue(sheet.isScenariosLocked()); + sheet.lockScenarios(false); + assertFalse(sheet.isScenariosLocked()); } public void testWriteSelectLockedCells() throws Exception { @@ -179,6 +204,8 @@ public class TestSheetProtection extends TestCase { assertFalse(sheet.isSelectLockedCellsLocked()); sheet.enableLocking(); assertTrue(sheet.isSelectLockedCellsLocked()); + sheet.lockSelectLockedCells(false); + assertFalse(sheet.isSelectLockedCellsLocked()); } public void testWriteSelectUnlockedCells() throws Exception { @@ -187,6 +214,8 @@ public class TestSheetProtection extends TestCase { assertFalse(sheet.isSelectUnlockedCellsLocked()); sheet.enableLocking(); assertTrue(sheet.isSelectUnlockedCellsLocked()); + sheet.lockSelectUnlockedCells(false); + assertFalse(sheet.isSelectUnlockedCellsLocked()); } public void testWriteSelectEnableLocking() throws Exception { diff --git a/src/ooxml/testcases/org/apache/poi/xssf/TestWorkbookProtection.java b/src/ooxml/testcases/org/apache/poi/xssf/TestWorkbookProtection.java index 642fe30748..969061932b 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/TestWorkbookProtection.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/TestWorkbookProtection.java @@ -16,42 +16,94 @@ ==================================================================== */ package org.apache.poi.xssf; -import java.io.File; - -import java.io.FileInputStream; -import java.io.FileOutputStream; - -import junit.framework.TestCase; - -import org.apache.poi.util.TempFile; +import static org.apache.poi.xssf.XSSFTestDataSamples.openSampleWorkbook; +import static org.apache.poi.xssf.XSSFTestDataSamples.writeOutAndReadBack; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.apache.poi.poifs.crypt.CryptoFunctions; +import org.apache.poi.poifs.crypt.HashAlgorithm; import org.apache.poi.xssf.usermodel.XSSFWorkbook; - -public class TestWorkbookProtection extends TestCase { - - public void testShouldReadWorkbookProtection() throws Exception { - XSSFWorkbook workbook = XSSFTestDataSamples.openSampleWorkbook("workbookProtection_not_protected.xlsx"); +import org.junit.Test; + +public class TestWorkbookProtection { + + @Test + public void workbookAndRevisionPassword() throws Exception { + XSSFWorkbook workbook; + String password = "test"; + + // validate password with an actual office file (Excel 2010) + workbook = openSampleWorkbook("workbookProtection-workbook_password_user_range-2010.xlsx"); + assertTrue(workbook.validateWorkbookPassword(password)); + + // validate with another office file (Excel 2013) + workbook = openSampleWorkbook("workbookProtection-workbook_password-2013.xlsx"); + assertTrue(workbook.validateWorkbookPassword(password)); + + + workbook = openSampleWorkbook("workbookProtection_not_protected.xlsx"); + + // setting a null password shouldn't introduce the protection element + workbook.setWorkbookPassword(null, null); + assertNull(workbook.getCTWorkbook().getWorkbookProtection()); + + // compare the hashes + workbook.setWorkbookPassword(password, null); + int hashVal = CryptoFunctions.createXorVerifier1(password); + int actualVal = Integer.parseInt(workbook.getCTWorkbook().getWorkbookProtection().xgetWorkbookPassword().getStringValue(),16); + assertEquals(hashVal, actualVal); + assertTrue(workbook.validateWorkbookPassword(password)); + + // removing the password again + workbook.setWorkbookPassword(null, null); + assertFalse(workbook.getCTWorkbook().getWorkbookProtection().isSetWorkbookPassword()); + + // removing the whole protection structure + workbook.unLock(); + assertNull(workbook.getCTWorkbook().getWorkbookProtection()); + + // setting a null password shouldn't introduce the protection element + workbook.setRevisionsPassword(null, null); + assertNull(workbook.getCTWorkbook().getWorkbookProtection()); + + // compare the hashes + password = "T\u0400ST\u0100passwordWhichIsLongerThan15Chars"; + workbook.setRevisionsPassword(password, null); + hashVal = CryptoFunctions.createXorVerifier1(password); + actualVal = Integer.parseInt(workbook.getCTWorkbook().getWorkbookProtection().xgetRevisionsPassword().getStringValue(),16); + assertEquals(hashVal, actualVal); + assertTrue(workbook.validateRevisionsPassword(password)); + } + + @Test + public void shouldReadWorkbookProtection() throws Exception { + XSSFWorkbook workbook = openSampleWorkbook("workbookProtection_not_protected.xlsx"); assertFalse(workbook.isStructureLocked()); assertFalse(workbook.isWindowsLocked()); assertFalse(workbook.isRevisionLocked()); - workbook = XSSFTestDataSamples.openSampleWorkbook("workbookProtection_workbook_structure_protected.xlsx"); + workbook = openSampleWorkbook("workbookProtection_workbook_structure_protected.xlsx"); assertTrue(workbook.isStructureLocked()); assertFalse(workbook.isWindowsLocked()); assertFalse(workbook.isRevisionLocked()); - workbook = XSSFTestDataSamples.openSampleWorkbook("workbookProtection_workbook_windows_protected.xlsx"); + workbook = openSampleWorkbook("workbookProtection_workbook_windows_protected.xlsx"); assertTrue(workbook.isWindowsLocked()); assertFalse(workbook.isStructureLocked()); assertFalse(workbook.isRevisionLocked()); - workbook = XSSFTestDataSamples.openSampleWorkbook("workbookProtection_workbook_revision_protected.xlsx"); + workbook = openSampleWorkbook("workbookProtection_workbook_revision_protected.xlsx"); assertTrue(workbook.isRevisionLocked()); assertFalse(workbook.isWindowsLocked()); assertFalse(workbook.isStructureLocked()); } - public void testShouldWriteStructureLock() throws Exception { - XSSFWorkbook workbook = XSSFTestDataSamples.openSampleWorkbook("workbookProtection_not_protected.xlsx"); + @Test + public void shouldWriteStructureLock() throws Exception { + XSSFWorkbook workbook = openSampleWorkbook("workbookProtection_not_protected.xlsx"); assertFalse(workbook.isStructureLocked()); workbook.lockStructure(); @@ -63,8 +115,9 @@ public class TestWorkbookProtection extends TestCase { assertFalse(workbook.isStructureLocked()); } - public void testShouldWriteWindowsLock() throws Exception { - XSSFWorkbook workbook = XSSFTestDataSamples.openSampleWorkbook("workbookProtection_not_protected.xlsx"); + @Test + public void shouldWriteWindowsLock() throws Exception { + XSSFWorkbook workbook = openSampleWorkbook("workbookProtection_not_protected.xlsx"); assertFalse(workbook.isWindowsLocked()); workbook.lockWindows(); @@ -76,8 +129,9 @@ public class TestWorkbookProtection extends TestCase { assertFalse(workbook.isWindowsLocked()); } - public void testShouldWriteRevisionLock() throws Exception { - XSSFWorkbook workbook = XSSFTestDataSamples.openSampleWorkbook("workbookProtection_not_protected.xlsx"); + @Test + public void shouldWriteRevisionLock() throws Exception { + XSSFWorkbook workbook = openSampleWorkbook("workbookProtection_not_protected.xlsx"); assertFalse(workbook.isRevisionLocked()); workbook.lockRevision(); @@ -89,22 +143,32 @@ public class TestWorkbookProtection extends TestCase { assertFalse(workbook.isRevisionLocked()); } + @SuppressWarnings("resource") + @Test + public void testHashPassword() throws Exception { + XSSFWorkbook wb = new XSSFWorkbook(); + wb.lockRevision(); + wb.setRevisionsPassword("test", HashAlgorithm.sha1); + + wb = writeOutAndReadBack(wb); + + assertTrue(wb.isRevisionLocked()); + assertTrue(wb.validateRevisionsPassword("test")); + } + + @SuppressWarnings("resource") + @Test public void testIntegration() throws Exception { XSSFWorkbook wb = new XSSFWorkbook(); wb.createSheet("Testing purpose sheet"); assertFalse(wb.isRevisionLocked()); wb.lockRevision(); + wb.setRevisionsPassword("test", null); - File tempFile = TempFile.createTempFile("workbookProtection", ".xlsx"); - FileOutputStream out = new FileOutputStream(tempFile); - wb.write(out); - out.close(); - - FileInputStream inputStream = new FileInputStream(tempFile); - XSSFWorkbook workbook = new XSSFWorkbook(inputStream); - inputStream.close(); - - assertTrue(workbook.isRevisionLocked()); + wb = writeOutAndReadBack(wb); + + assertTrue(wb.isRevisionLocked()); + assertTrue(wb.validateRevisionsPassword("test")); } } diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java index 6da347671c..38bc41101e 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java @@ -19,6 +19,8 @@ package org.apache.poi.xssf.usermodel; import static junit.framework.TestCase.assertNotNull; import static junit.framework.TestCase.assertTrue; +import static org.apache.poi.xssf.XSSFTestDataSamples.openSampleWorkbook; +import static org.apache.poi.xssf.XSSFTestDataSamples.writeOutAndReadBack; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; @@ -29,7 +31,8 @@ import static org.junit.Assert.fail; import java.util.List; import org.apache.poi.hssf.HSSFTestDataSamples; -import org.apache.poi.hssf.record.PasswordRecord; +import org.apache.poi.poifs.crypt.CryptoFunctions; +import org.apache.poi.poifs.crypt.HashAlgorithm; import org.apache.poi.ss.usermodel.AutoFilter; import org.apache.poi.ss.usermodel.BaseTestSheet; import org.apache.poi.ss.usermodel.Cell; @@ -41,7 +44,6 @@ import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.util.AreaReference; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.CellReference; -import org.apache.poi.util.HexDump; import org.apache.poi.xssf.SXSSFITestDataProvider; import org.apache.poi.xssf.XSSFITestDataProvider; import org.apache.poi.xssf.XSSFTestDataSamples; @@ -1068,13 +1070,27 @@ public final class TestXSSFSheet extends BaseTestSheet { assertTrue("sheet protection should be on", pr.isSetSheet()); assertTrue("object protection should be on", pr.isSetObjects()); assertTrue("scenario protection should be on", pr.isSetScenarios()); - String hash = String.valueOf(HexDump.shortToHex(PasswordRecord.hashPassword(password))).substring(2); - assertEquals("well known value for top secret hash should be "+ hash, hash, pr.xgetPassword().getStringValue()); + int hashVal = CryptoFunctions.createXorVerifier1(password); + int actualVal = Integer.parseInt(pr.xgetPassword().getStringValue(),16); + assertEquals("well known value for top secret hash should match", hashVal, actualVal); sheet.protectSheet(null); assertNull("protectSheet(null) should unset CTSheetProtection", sheet.getCTWorksheet().getSheetProtection()); } + @Test + public void protectSheet_lowlevel_2013() { + String password = "test"; + XSSFWorkbook wb = new XSSFWorkbook(); + XSSFSheet xs = wb.createSheet(); + xs.setSheetPassword(password, HashAlgorithm.sha384); + wb = writeOutAndReadBack(wb); + assertTrue(wb.getSheetAt(0).validateSheetPassword(password)); + + wb = openSampleWorkbook("workbookProtection-sheet_password-2013.xlsx"); + assertTrue(wb.getSheetAt(0).validateSheetPassword("pwd")); + } + @Test public void bug49966() { diff --git a/test-data/spreadsheet/workbookProtection-sheet_password-2013.xlsx b/test-data/spreadsheet/workbookProtection-sheet_password-2013.xlsx Binary files differnew file mode 100644 index 0000000000..691b2cc219 --- /dev/null +++ b/test-data/spreadsheet/workbookProtection-sheet_password-2013.xlsx diff --git a/test-data/spreadsheet/workbookProtection-workbook_password-2013.xlsx b/test-data/spreadsheet/workbookProtection-workbook_password-2013.xlsx Binary files differnew file mode 100644 index 0000000000..978210787c --- /dev/null +++ b/test-data/spreadsheet/workbookProtection-workbook_password-2013.xlsx diff --git a/test-data/spreadsheet/workbookProtection-workbook_password_user_range-2010.xlsx b/test-data/spreadsheet/workbookProtection-workbook_password_user_range-2010.xlsx Binary files differnew file mode 100644 index 0000000000..2712913380 --- /dev/null +++ b/test-data/spreadsheet/workbookProtection-workbook_password_user_range-2010.xlsx |