]> source.dussan.org Git - poi.git/commitdiff
Bug 51483 - XSSF locking of specific features not working
authorAndreas Beeker <kiwiwings@apache.org>
Thu, 4 Sep 2014 22:50:28 +0000 (22:50 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Thu, 4 Sep 2014 22:50:28 +0000 (22:50 +0000)
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:
src/java/org/apache/poi/hssf/record/PasswordRecord.java
src/java/org/apache/poi/hssf/record/aggregates/WorksheetProtectionBlock.java
src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java
src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java
src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java
src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java
src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFPaswordHelper.java [new file with mode: 0644]
src/ooxml/testcases/org/apache/poi/xssf/TestSheetProtection.java
src/ooxml/testcases/org/apache/poi/xssf/TestWorkbookProtection.java
src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java
test-data/spreadsheet/workbookProtection-sheet_password-2013.xlsx [new file with mode: 0644]
test-data/spreadsheet/workbookProtection-workbook_password-2013.xlsx [new file with mode: 0644]
test-data/spreadsheet/workbookProtection-workbook_password_user_range-2010.xlsx [new file with mode: 0644]

index 9baff6f97d443db500e7252854ca7815fc412270..c38a1230cad71bd9521303790e74fbf7ccf54440 100644 (file)
@@ -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);
     }
 
     /**
index f838485f74d65d079f228438d8a1236b2e5a06e3..275fbf123a6b3dbd2f16e92d3391ef84d3c03dd3 100644 (file)
@@ -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);
index f9f970ade9fd18dad5b56efc11ddb01181aee5d5..ffb7498f3fabbe7d002dc87667578b325423b1ec 100644 (file)
@@ -180,14 +180,20 @@ public class CryptoFunctions {
     }\r
 \r
     /**\r
-     * \r
+     * Initialize a new cipher object with the given cipher properties\r
+     * If the given algorithm is not implemented in the JCE, it will try to load it from the bouncy castle\r
+     * provider.\r
      *\r
-     * @param key\r
-     * @param chain\r
-     * @param vec\r
+     * @param key the secrect key\r
+     * @param cipherAlgorithm the cipher algorithm\r
+     * @param chain the chaining mode\r
+     * @param vec the initialization vector (IV), can be null\r
      * @param cipherMode Cipher.DECRYPT_MODE or Cipher.ENCRYPT_MODE\r
+     * @param padding\r
      * @return the requested cipher\r
      * @throws GeneralSecurityException\r
+     * @throws EncryptedDocumentException if the initialization failed or if an algorithm was specified,\r
+     *   which depends on a missing bouncy castle provider \r
      */\r
     public static Cipher getCipher(SecretKey key, CipherAlgorithm cipherAlgorithm, ChainingMode chain, byte[] vec, int cipherMode, String padding) {\r
         int keySizeInBytes = key.getEncoded().length;\r
@@ -226,10 +232,26 @@ public class CryptoFunctions {
         }\r
     }    \r
     \r
+    /**\r
+     * Returns a new byte array with a truncated to the given size. \r
+     * If the hash has less then size bytes, it will be filled with 0x36-bytes\r
+     *\r
+     * @param hash the to be truncated/filled hash byte array\r
+     * @param size the size of the returned byte array\r
+     * @return the padded hash\r
+     */\r
     public static byte[] getBlock36(byte[] hash, int size) {\r
         return getBlockX(hash, size, (byte)0x36);\r
     }\r
 \r
+    /**\r
+     * Returns a new byte array with a truncated to the given size. \r
+     * If the hash has less then size bytes, it will be filled with 0-bytes\r
+     *\r
+     * @param hash the to be truncated/filled hash byte array\r
+     * @param size the size of the returned byte array\r
+     * @return the padded hash\r
+     */\r
     public static byte[] getBlock0(byte[] hash, int size) {\r
         return getBlockX(hash, size, (byte)0);\r
     }\r
@@ -331,11 +353,11 @@ public class CryptoFunctions {
         byte[] generatedKey = new byte[4];\r
 \r
         //Maximum length of the password is 15 chars.\r
-        final int intMaxPasswordLength = 15; \r
+        final int maxPasswordLength = 15; \r
         \r
         if (!"".equals(password)) {\r
             // Truncate the password to 15 characters\r
-            password = password.substring(0, Math.min(password.length(), intMaxPasswordLength));\r
+            password = password.substring(0, Math.min(password.length(), maxPasswordLength));\r
 \r
             // Construct a new NULL-terminated string consisting of single-byte characters:\r
             //  -- > Get the single-byte values by iterating through the Unicode characters of the truncated Password.\r
@@ -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 \r
             //          the Encryption Matrix\r
             for (int i = 0; i < arrByteChars.length; i++) {\r
-                int tmp = intMaxPasswordLength - arrByteChars.length + i;\r
+                int tmp = maxPasswordLength - arrByteChars.length + i;\r
                 for (int intBit = 0; intBit < 7; intBit++) {\r
                     if ((arrByteChars[i] & (0x0001 << intBit)) != 0) {\r
                         highOrderWord ^= EncryptionMatrix[tmp][intBit];\r
@@ -369,22 +391,28 @@ public class CryptoFunctions {
             \r
             // Compute the low-order word of the new key:\r
             \r
-            // Initialize with 0\r
-            int lowOrderWord = 0;\r
+            // SET Verifier TO 0x0000\r
+            short verifier = 0;\r
 \r
-            // For each character in the password, going backwards\r
-            for (int i = arrByteChars.length - 1; i >= 0; i--) {\r
-                // low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR character\r
-                lowOrderWord = (((lowOrderWord >> 14) & 0x0001) | ((lowOrderWord << 1) & 0x7FFF)) ^ arrByteChars[i];\r
+            // FOR EACH PasswordByte IN PasswordArray IN REVERSE ORDER\r
+            for (int i = arrByteChars.length-1; i >= 0; i--) {\r
+                // SET Verifier TO Intermediate3 BITWISE XOR PasswordByte\r
+                verifier = rotateLeftBase15Bit(verifier);\r
+                verifier ^= arrByteChars[i];\r
             }\r
 \r
-            // Lastly,low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR password length XOR 0xCE4B.\r
-            lowOrderWord = (((lowOrderWord >> 14) & 0x0001) | ((lowOrderWord << 1) & 0x7FFF)) ^ arrByteChars.length ^ 0xCE4B;\r
+            // as we haven't prepended the password length into the input array\r
+            // we need to do it now separately ...\r
+            verifier = rotateLeftBase15Bit(verifier);\r
+            verifier ^= arrByteChars.length;\r
+            \r
+            // RETURN Verifier BITWISE XOR 0xCE4B\r
+            verifier ^= 0xCE4B; // (0x8000 | ('N' << 8) | 'K')\r
 \r
             // The byte order of the result shall be reversed [password "Example": 0x64CEED7E becomes 7EEDCE64],\r
             // and that value shall be hashed as defined by the attribute values.\r
             \r
-            LittleEndian.putShort(generatedKey, 0, (short)lowOrderWord);\r
+            LittleEndian.putShort(generatedKey, 0, verifier);\r
             LittleEndian.putShort(generatedKey, 2, (short)highOrderWord);\r
         }\r
         \r
@@ -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>\r
      * \r
      * @param password the password\r
-     * @return the verifier\r
+     * @return the verifier (actually a short value)\r
      */\r
     public static int createXorVerifier1(String password) {\r
         // the verifier for method 1 is part of the verifier for method 2\r
@@ -480,4 +508,25 @@ public class CryptoFunctions {
     private static byte rotateLeft(byte bits, int shift) {\r
         return (byte)(((bits & 0xff) << shift) | ((bits & 0xff) >>> (8 - shift)));\r
     }\r
+    \r
+    private static short rotateLeftBase15Bit(short verifier) {\r
+        /*\r
+         * IF (Verifier BITWISE AND 0x4000) is 0x0000\r
+         *    SET Intermediate1 TO 0\r
+         * ELSE\r
+         *    SET Intermediate1 TO 1\r
+         * ENDIF\r
+         */\r
+        short intermediate1 = (short)(((verifier & 0x4000) == 0) ? 0 : 1);\r
+        /*\r
+         *  SET Intermediate2 TO Verifier MULTIPLED BY 2\r
+         *  SET most significant bit of Intermediate2 TO 0\r
+         */\r
+        short intermediate2 = (short)((verifier<<1) & 0x7FFF);\r
+        /*\r
+         *  SET Intermediate3 TO Intermediate1 BITWISE OR Intermediate2\r
+         */\r
+        short intermediate3 = (short)(intermediate1 | intermediate2);\r
+        return intermediate3;\r
+    }\r
 }\r
index 51217184baf0634c119c265c6a89e5fe7c2de099..61608822f8f11fedf51a472fc5362f4de85da6ec 100644 (file)
@@ -64,4 +64,11 @@ public enum HashAlgorithm {
         }\r
         throw new EncryptedDocumentException("hash algorithm not found");\r
     }\r
+    \r
+    public static HashAlgorithm fromString(String string) {\r
+        for (HashAlgorithm ha : values()) {\r
+            if (ha.ecmaString.equalsIgnoreCase(string) || ha.jceId.equalsIgnoreCase(string)) return ha;\r
+        }\r
+        throw new EncryptedDocumentException("hash algorithm not found");\r
+    }\r
 }
\ No newline at end of file
index 60ecf7bb9ff1da0be892ce5191cc748b65f0100b..9da8a6955a45d9fe603d67df380c1a24b8935def 100644 (file)
@@ -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) {
index f5dc36b336c4eb96196eb323a88cc76ac9cd2324..8beb39aa98c70116d2b537948580be06f47c592c 100644 (file)
@@ -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 (file)
index 0000000..c887129
--- /dev/null
@@ -0,0 +1,128 @@
+/*\r
+ *  ====================================================================\r
+ * Licensed to the Apache Software Foundation (ASF) under one or more\r
+ * contributor license agreements.  See the NOTICE file distributed with\r
+ * this work for additional information regarding copyright ownership.\r
+ * The ASF licenses this file to You under the Apache License, Version 2.0\r
+ * (the "License"); you may not use this file except in compliance with\r
+ * the License.  You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ * ====================================================================\r
+ */\r
+\r
+package org.apache.poi.xssf.usermodel.helpers;\r
+\r
+import java.security.SecureRandom;\r
+import java.util.Arrays;\r
+\r
+import javax.xml.bind.DatatypeConverter;\r
+import javax.xml.namespace.QName;\r
+\r
+import org.apache.poi.poifs.crypt.CryptoFunctions;\r
+import org.apache.poi.poifs.crypt.HashAlgorithm;\r
+import org.apache.xmlbeans.XmlCursor;\r
+import org.apache.xmlbeans.XmlObject;\r
+\r
+public class XSSFPaswordHelper {\r
+    /**\r
+     * Sets the XORed or hashed password \r
+     *\r
+     * @param xobj the xmlbeans object which contains the password attributes\r
+     * @param password the password, if null, the password attributes will be removed\r
+     * @param hashAlgo the hash algorithm, if null the password will be XORed\r
+     * @param prefix the prefix of the password attributes, may be null\r
+     */\r
+    public static void setPassword(XmlObject xobj, String password, HashAlgorithm hashAlgo, String prefix) {\r
+        XmlCursor cur = xobj.newCursor();\r
+\r
+        if (password == null) {\r
+            cur.removeAttribute(getAttrName(prefix, "password"));\r
+            cur.removeAttribute(getAttrName(prefix, "algorithmName"));\r
+            cur.removeAttribute(getAttrName(prefix, "hashValue"));\r
+            cur.removeAttribute(getAttrName(prefix, "saltValue"));\r
+            cur.removeAttribute(getAttrName(prefix, "spinCount"));\r
+            return;\r
+        } \r
+        \r
+        cur.toFirstContentToken();\r
+        if (hashAlgo == null) {\r
+            int hash = CryptoFunctions.createXorVerifier1(password);\r
+            cur.insertAttributeWithValue(getAttrName(prefix, "password"), Integer.toHexString(hash).toUpperCase());\r
+        } else {\r
+            SecureRandom random = new SecureRandom(); \r
+            byte salt[] = random.generateSeed(16);\r
+    \r
+            // Iterations specifies the number of times the hashing function shall be iteratively run (using each\r
+            // iteration's result as the input for the next iteration).\r
+            int spinCount = 100000;\r
+\r
+            // Implementation Notes List:\r
+            // --> In this third stage, the reversed byte order legacy hash from the second stage shall\r
+            //     be converted to Unicode hex string representation\r
+            byte hash[] = CryptoFunctions.hashPassword(password, hashAlgo, salt, spinCount, false);\r
+            \r
+            cur.insertAttributeWithValue(getAttrName(prefix, "algorithmName"), hashAlgo.jceId); \r
+            cur.insertAttributeWithValue(getAttrName(prefix, "hashValue"), DatatypeConverter.printBase64Binary(hash));\r
+            cur.insertAttributeWithValue(getAttrName(prefix, "saltValue"), DatatypeConverter.printBase64Binary(salt));\r
+            cur.insertAttributeWithValue(getAttrName(prefix, "spinCount"), ""+spinCount);\r
+        }\r
+        cur.dispose();\r
+    }\r
+\r
+    /**\r
+     * Validates the password, i.e.\r
+     * calculates the hash of the given password and compares it against the stored hash\r
+     *\r
+     * @param xobj the xmlbeans object which contains the password attributes\r
+     * @param password the password, if null the method will always return false,\r
+     *  even if there's no password set\r
+     * @param prefix the prefix of the password attributes, may be null\r
+     * \r
+     * @return true, if the hashes match\r
+     */\r
+    public static boolean validatePassword(XmlObject xobj, String password, String prefix) {\r
+        // TODO: is "velvetSweatshop" the default password?\r
+        if (password == null) return false;\r
+        \r
+        XmlCursor cur = xobj.newCursor();\r
+        String xorHashVal = cur.getAttributeText(getAttrName(prefix, "password"));\r
+        String algoName = cur.getAttributeText(getAttrName(prefix, "algorithmName"));\r
+        String hashVal = cur.getAttributeText(getAttrName(prefix, "hashValue"));\r
+        String saltVal = cur.getAttributeText(getAttrName(prefix, "saltValue"));\r
+        String spinCount = cur.getAttributeText(getAttrName(prefix, "spinCount"));\r
+        cur.dispose();\r
+\r
+        if (xorHashVal != null) {\r
+            int hash1 = Integer.parseInt(xorHashVal, 16);\r
+            int hash2 = CryptoFunctions.createXorVerifier1(password);\r
+            return hash1 == hash2;\r
+        } else {\r
+            if (hashVal == null || algoName == null || saltVal == null || spinCount == null) {\r
+                return false;\r
+            }\r
+            \r
+            byte hash1[] = DatatypeConverter.parseBase64Binary(hashVal);\r
+            HashAlgorithm hashAlgo = HashAlgorithm.fromString(algoName);\r
+            byte salt[] = DatatypeConverter.parseBase64Binary(saltVal);\r
+            int spinCnt = Integer.parseInt(spinCount);\r
+            byte hash2[] = CryptoFunctions.hashPassword(password, hashAlgo, salt, spinCnt, false);\r
+            return Arrays.equals(hash1, hash2);\r
+        }\r
+    }\r
+    \r
+    \r
+    private static QName getAttrName(String prefix, String name) {\r
+        if (prefix == null || "".equals(prefix)) {\r
+            return new QName(name);\r
+        } else {\r
+            return new QName(prefix+Character.toUpperCase(name.charAt(0))+name.substring(1));\r
+        }\r
+    }\r
+}\r
index 156c0dec4943a104471d051cfc18e82dbd1a513f..f90804fa4ca9601969671a7bbef0c4a2682fa110 100644 (file)
 ==================================================================== */
 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 {
index 642fe30748ec2653d06d01930d30f5e61837fbf3..969061932b48863fef2fd1bcb9b5c160dc336fd0 100644 (file)
 ==================================================================== */
 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"));
        }
 }
index 6da347671c30fb9e46cc2d921baa0b77d2d1d91d..38bc41101ee13633fb6f1489e85deedefc91894b 100644 (file)
@@ -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 (file)
index 0000000..691b2cc
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 (file)
index 0000000..9782107
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 (file)
index 0000000..2712913
Binary files /dev/null and b/test-data/spreadsheet/workbookProtection-workbook_password_user_range-2010.xlsx differ