Fix zero-padding and handling of empty passwords (meaning protection on, but no password to remove it) for XSSF workbook protection.

https://bz.apache.org/bugzilla/show_bug.cgi?id=59920

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1754744 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java b/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java
index f681f3a..69f8d67 100644
--- a/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java
+++ b/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java
@@ -374,20 +374,22 @@
         // SET Verifier TO 0x0000

         short verifier = 0;

 

-        // FOR EACH PasswordByte IN PasswordArray IN REVERSE ORDER

-        for (int i = arrByteChars.length-1; i >= 0; i--) {

-            // SET Verifier TO Intermediate3 BITWISE XOR PasswordByte

+        if (!"".equals(password)) {

+            // 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];

+            }

+    

+            // as we haven't prepended the password length into the input array

+            // we need to do it now separately ...

             verifier = rotateLeftBase15Bit(verifier);

-            verifier ^= arrByteChars[i];

+            verifier ^= arrByteChars.length;

+            

+            // RETURN Verifier BITWISE XOR 0xCE4B

+            verifier ^= 0xCE4B; // (0x8000 | ('N' << 8) | 'K')

         }

-

-        // 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')

         

         return verifier & 0xFFFF;

     }

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
index c910499..fd6e2db 100644
--- a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFPaswordHelper.java
+++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFPaswordHelper.java
@@ -55,8 +55,8 @@
         cur.toFirstContentToken();

         if (hashAlgo == null) {

             int hash = CryptoFunctions.createXorVerifier1(password);

-            cur.insertAttributeWithValue(getAttrName(prefix, "password"), 

-                                         Integer.toHexString(hash).toUpperCase(Locale.ROOT));

+            cur.insertAttributeWithValue(getAttrName(prefix, "password"),

+                                         String.format(Locale.ROOT, "%04X", hash).toUpperCase(Locale.ROOT));

         } else {

             SecureRandom random = new SecureRandom(); 

             byte salt[] = random.generateSeed(16);

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 a9b0d1f..689e999 100644
--- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java
+++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java
@@ -80,6 +80,7 @@
 import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTXf;
 import org.openxmlformats.schemas.spreadsheetml.x2006.main.STCalcMode;
 import org.openxmlformats.schemas.spreadsheetml.x2006.main.STPane;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.STUnsignedShortHex;
 
 
 public final class TestXSSFSheet extends BaseTestXSheet {
@@ -1100,6 +1101,30 @@
     }
 
     @Test
+    public void protectSheet_emptyPassword() throws IOException {
+        XSSFWorkbook wb = new XSSFWorkbook();
+        XSSFSheet sheet = wb.createSheet();
+        CTSheetProtection pr = sheet.getCTWorksheet().getSheetProtection();
+        assertNull("CTSheetProtection should be null by default", pr);
+        String password = "";
+        sheet.protectSheet(password);
+        pr = sheet.getCTWorksheet().getSheetProtection();
+        assertNotNull("CTSheetProtection should be not null", pr);
+        assertTrue("sheet protection should be on", pr.isSetSheet());
+        assertTrue("object protection should be on", pr.isSetObjects());
+        assertTrue("scenario protection should be on", pr.isSetScenarios());
+        int hashVal = CryptoFunctions.createXorVerifier1(password);
+        STUnsignedShortHex xpassword = pr.xgetPassword();
+        int actualVal = Integer.parseInt(xpassword.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());
+
+        wb.close();
+    }
+
+    @Test
     public void protectSheet_lowlevel_2013() throws IOException {
         String password = "test";
         XSSFWorkbook wb1 = new XSSFWorkbook();