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 or disable Autofilters locking.
+ * This does not modify sheet protection status.
+ * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
+ */
+ public void lockAutoFilter(boolean enabled) {
+ safeGetProtectionField().setAutoFilter(enabled);
}
/**
* Enable Deleting columns locking.
- * This does not modify sheet protection status.
- * To enforce this locking, call {@link #enableLocking()}
+ * @deprecated use {@link #lockDeleteColumns(boolean)}
*/
public void lockDeleteColumns() {
- createProtectionFieldIfNotPresent();
- worksheet.getSheetProtection().setDeleteColumns(true);
+ lockDeleteColumns(true);
+ }
+
+ /**
+ * Enable or disable Deleting columns locking.
+ * This does not modify sheet protection status.
+ * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
+ */
+ public void lockDeleteColumns(boolean enabled) {
+ safeGetProtectionField().setDeleteColumns(enabled);
}
/**
* Enable Deleting rows locking.
- * This does not modify sheet protection status.
- * To enforce this locking, call {@link #enableLocking()}
+ * @deprecated use {@link #lockDeleteRows(boolean)}
*/
public void lockDeleteRows() {
- createProtectionFieldIfNotPresent();
- worksheet.getSheetProtection().setDeleteRows(true);
+ lockDeleteRows(true);
+ }
+
+ /**
+ * Enable or disable Deleting rows locking.
+ * This does not modify sheet protection status.
+ * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
+ */
+ public void lockDeleteRows(boolean enabled) {
+ safeGetProtectionField().setDeleteRows(enabled);
}
/**
* Enable Formatting cells locking.
- * This does not modify sheet protection status.
- * To enforce this locking, call {@link #enableLocking()}
+ * @deprecated use {@link #lockFormatCells(boolean)}
*/
public void lockFormatCells() {
- createProtectionFieldIfNotPresent();
- worksheet.getSheetProtection().setDeleteColumns(true);
+ lockFormatCells(true);
+ }
+
+ /**
+ * Enable or disable Formatting cells locking.
+ * This does not modify sheet protection status.
+ * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
+ */
+ public void lockFormatCells(boolean enabled) {
+ safeGetProtectionField().setFormatCells(enabled);
}
/**
* Enable Formatting columns locking.
- * This does not modify sheet protection status.
- * To enforce this locking, call {@link #enableLocking()}
+ * @deprecated use {@link #lockFormatColumns(boolean)}
*/
public void lockFormatColumns() {
- createProtectionFieldIfNotPresent();
- worksheet.getSheetProtection().setFormatColumns(true);
+ lockFormatColumns(true);
+ }
+
+ /**
+ * Enable or disable Formatting columns locking.
+ * This does not modify sheet protection status.
+ * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
+ */
+ public void lockFormatColumns(boolean enabled) {
+ safeGetProtectionField().setFormatColumns(enabled);
}
/**
* Enable Formatting rows locking.
- * This does not modify sheet protection status.
- * To enforce this locking, call {@link #enableLocking()}
+ * @deprecated use {@link #lockFormatRows(boolean)}
*/
public void lockFormatRows() {
- createProtectionFieldIfNotPresent();
- worksheet.getSheetProtection().setFormatRows(true);
+ lockFormatRows(true);
+ }
+
+ /**
+ * Enable or disable Formatting rows locking.
+ * This does not modify sheet protection status.
+ * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
+ */
+ public void lockFormatRows(boolean enabled) {
+ safeGetProtectionField().setFormatRows(enabled);
}
/**
* Enable Inserting columns locking.
- * This does not modify sheet protection status.
- * To enforce this locking, call {@link #enableLocking()}
+ * @deprecated use {@link #lockInsertColumns(boolean)}
*/
public void lockInsertColumns() {
- createProtectionFieldIfNotPresent();
- worksheet.getSheetProtection().setInsertColumns(true);
+ lockInsertColumns(true);
+ }
+
+ /**
+ * Enable or disable Inserting columns locking.
+ * This does not modify sheet protection status.
+ * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
+ */
+ public void lockInsertColumns(boolean enabled) {
+ safeGetProtectionField().setInsertColumns(enabled);
}
/**
* Enable Inserting hyperlinks locking.
- * This does not modify sheet protection status.
- * To enforce this locking, call {@link #enableLocking()}
+ * @deprecated use {@link #lockInsertHyperlinks(boolean)}
*/
public void lockInsertHyperlinks() {
- createProtectionFieldIfNotPresent();
- worksheet.getSheetProtection().setInsertHyperlinks(true);
+ lockInsertHyperlinks(true);
+ }
+
+ /**
+ * Enable or disable Inserting hyperlinks locking.
+ * This does not modify sheet protection status.
+ * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
+ */
+ public void lockInsertHyperlinks(boolean enabled) {
+ safeGetProtectionField().setInsertHyperlinks(enabled);
}
/**
* Enable Inserting rows locking.
- * This does not modify sheet protection status.
- * To enforce this locking, call {@link #enableLocking()}
+ * @deprecated use {@link #lockInsertRows(boolean)}
*/
public void lockInsertRows() {
- createProtectionFieldIfNotPresent();
- worksheet.getSheetProtection().setInsertRows(true);
+ lockInsertRows(true);
+ }
+
+ /**
+ * Enable or disable Inserting rows locking.
+ * This does not modify sheet protection status.
+ * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
+ */
+ public void lockInsertRows(boolean enabled) {
+ safeGetProtectionField().setInsertRows(enabled);
}
/**
* Enable Pivot Tables locking.
- * This does not modify sheet protection status.
- * To enforce this locking, call {@link #enableLocking()}
+ * @deprecated use {@link #lockPivotTables(boolean)}
*/
public void lockPivotTables() {
- createProtectionFieldIfNotPresent();
- worksheet.getSheetProtection().setPivotTables(true);
+ lockPivotTables(true);
+ }
+
+ /**
+ * Enable or disable Pivot Tables locking.
+ * This does not modify sheet protection status.
+ * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
+ */
+ public void lockPivotTables(boolean enabled) {
+ safeGetProtectionField().setPivotTables(enabled);
}
/**
* Enable Sort locking.
- * This does not modify sheet protection status.
- * To enforce this locking, call {@link #enableLocking()}
+ * @deprecated use {@link #lockSort(boolean)}
*/
public void lockSort() {
- createProtectionFieldIfNotPresent();
- worksheet.getSheetProtection().setSort(true);
+ lockSort(true);
+ }
+
+ /**
+ * Enable or disable Sort locking.
+ * This does not modify sheet protection status.
+ * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
+ */
+ public void lockSort(boolean enabled) {
+ safeGetProtectionField().setSort(enabled);
}
/**
* Enable Objects locking.
- * This does not modify sheet protection status.
- * To enforce this locking, call {@link #enableLocking()}
+ * @deprecated use {@link #lockObjects(boolean)}
*/
public void lockObjects() {
- createProtectionFieldIfNotPresent();
- worksheet.getSheetProtection().setObjects(true);
+ lockObjects(true);
+ }
+
+ /**
+ * Enable or disable Objects locking.
+ * This does not modify sheet protection status.
+ * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()}
+ */
+ public void lockObjects(boolean enabled) {
+ safeGetProtectionField().setObjects(enabled);
}
/**
* Enable Scenarios locking.
- * This does not modify sheet protection status.
- * To enforce this locking, call {@link #enableLocking()}
+ * @deprecated use {@link #lockScenarios(boolean)}
*/
public void lockScenarios() {
- createProtectionFieldIfNotPresent();
- worksheet.getSheetProtection().setScenarios(true);
+ lockScenarios(true);
+ }
+
+ /**
+ * Enable or disable Scenarios locking.
+ * This does not modify sheet protection status.
+ * 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.
- * This does not modify sheet protection status.
- * To enforce this locking, call {@link #enableLocking()}
+ * @deprecated use {@link #lockSelectLockedCells(boolean)}
*/
public void lockSelectLockedCells() {
- createProtectionFieldIfNotPresent();
- worksheet.getSheetProtection().setSelectLockedCells(true);
+ lockSelectLockedCells(true);
+ }
+
+ /**
+ * Enable or disable Selection of locked cells locking.
+ * This does not modify sheet protection status.
+ * 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.
- * This does not modify sheet protection status.
- * To enforce this locking, call {@link #enableLocking()}
+ * @deprecated use {@link #lockSelectUnlockedCells(boolean)}
*/
public void lockSelectUnlockedCells() {
- createProtectionFieldIfNotPresent();
- worksheet.getSheetProtection().setSelectUnlockedCells(true);
+ lockSelectUnlockedCells(true);
}
- private void createProtectionFieldIfNotPresent() {
- if (worksheet.getSheetProtection() == null) {
- worksheet.setSheetProtection(CTSheetProtection.Factory.newInstance());
+ /**
+ * 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 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 org.apache.poi.xssf.usermodel.XSSFWorkbook;
-
import junit.framework.TestCase;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
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 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 java.io.FileInputStream;
-import java.io.FileOutputStream;
-
-import junit.framework.TestCase;
-
-import org.apache.poi.util.TempFile;
+import org.apache.poi.poifs.crypt.CryptoFunctions;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.junit.Test;
-public class TestWorkbookProtection extends TestCase {
+public class TestWorkbookProtection {
- public void testShouldReadWorkbookProtection() throws Exception {
- XSSFWorkbook workbook = XSSFTestDataSamples.openSampleWorkbook("workbookProtection_not_protected.xlsx");
+ @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