From: Andreas Beeker Date: Thu, 4 Sep 2014 22:50:28 +0000 (+0000) Subject: Bug 51483 - XSSF locking of specific features not working X-Git-Tag: REL_3_11_BETA3~81 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=d71574d53115d54f2a30b602ea131e8e1671850c;p=poi.git 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 --- 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 2.3.7.4 Binary Document Password Verifier Derivation Method 2 * * @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,18 +1082,27 @@ 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 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 new file mode 100644 index 0000000000..691b2cc219 Binary files /dev/null and b/test-data/spreadsheet/workbookProtection-sheet_password-2013.xlsx differ diff --git a/test-data/spreadsheet/workbookProtection-workbook_password-2013.xlsx b/test-data/spreadsheet/workbookProtection-workbook_password-2013.xlsx new file mode 100644 index 0000000000..978210787c Binary files /dev/null and b/test-data/spreadsheet/workbookProtection-workbook_password-2013.xlsx differ diff --git a/test-data/spreadsheet/workbookProtection-workbook_password_user_range-2010.xlsx b/test-data/spreadsheet/workbookProtection-workbook_password_user_range-2010.xlsx new file mode 100644 index 0000000000..2712913380 Binary files /dev/null and b/test-data/spreadsheet/workbookProtection-workbook_password_user_range-2010.xlsx differ