==================================================================== */\r
package org.apache.poi.poifs.crypt;\r
\r
-import java.io.UnsupportedEncodingException;\r
+import java.nio.charset.Charset;\r
import java.security.DigestException;\r
import java.security.GeneralSecurityException;\r
import java.security.MessageDigest;\r
* @return the hashed password\r
*/\r
public static byte[] hashPassword(String password, HashAlgorithm hashAlgorithm, byte salt[], int spinCount) {\r
+ return hashPassword(password, hashAlgorithm, salt, spinCount, true);\r
+ }\r
+ \r
+ /**\r
+ * Generalized method for read and write protection hash generation.\r
+ * The difference is, read protection uses the order iterator then hash in the hash loop, whereas write protection\r
+ * uses first the last hash value and then the current iterator value\r
+ *\r
+ * @param password\r
+ * @param hashAlgorithm\r
+ * @param salt\r
+ * @param spinCount\r
+ * @param iteratorFirst if true, the iterator is hashed before the n-1 hash value,\r
+ * if false the n-1 hash value is applied first\r
+ * @return the hashed password\r
+ */\r
+ public static byte[] hashPassword(String password, HashAlgorithm hashAlgorithm, byte salt[], int spinCount, boolean iteratorFirst) {\r
// If no password was given, use the default\r
if (password == null) {\r
password = Decryptor.DEFAULT_PASSWORD;\r
hashAlg.update(salt);\r
byte[] hash = hashAlg.digest(getUtf16LeString(password));\r
byte[] iterator = new byte[LittleEndianConsts.INT_SIZE];\r
+\r
+ byte[] first = (iteratorFirst ? iterator : hash);\r
+ byte[] second = (iteratorFirst ? hash : iterator);\r
\r
try {\r
for (int i = 0; i < spinCount; i++) {\r
LittleEndian.putInt(iterator, 0, i);\r
hashAlg.reset();\r
- hashAlg.update(iterator);\r
- hashAlg.update(hash);\r
+ hashAlg.update(first);\r
+ hashAlg.update(second);\r
hashAlg.digest(hash, 0, hash.length); // don't create hash buffer everytime new\r
}\r
} catch (DigestException e) {\r
}\r
\r
public static byte[] getUtf16LeString(String str) {\r
- try {\r
- return str.getBytes("UTF-16LE");\r
- } catch (UnsupportedEncodingException e) {\r
- throw new EncryptedDocumentException(e);\r
- }\r
+ return str.getBytes(Charset.forName("UTF-16LE"));\r
}\r
\r
public static MessageDigest getMessageDigest(HashAlgorithm hashAlgorithm) {\r
throw new EncryptedDocumentException("Only the BouncyCastle provider supports your encryption settings - please add it to the classpath.");\r
}\r
}\r
+\r
+\r
+ private static final int InitialCodeArray[] = { \r
+ 0xE1F0, 0x1D0F, 0xCC9C, 0x84C0, 0x110C, 0x0E10, 0xF1CE, \r
+ 0x313E, 0x1872, 0xE139, 0xD40F, 0x84F9, 0x280C, 0xA96A, \r
+ 0x4EC3\r
+ };\r
+ \r
+ private static final int EncryptionMatrix[][] = {\r
+ /* char 1 */ {0xAEFC, 0x4DD9, 0x9BB2, 0x2745, 0x4E8A, 0x9D14, 0x2A09},\r
+ /* char 2 */ {0x7B61, 0xF6C2, 0xFDA5, 0xEB6B, 0xC6F7, 0x9DCF, 0x2BBF},\r
+ /* char 3 */ {0x4563, 0x8AC6, 0x05AD, 0x0B5A, 0x16B4, 0x2D68, 0x5AD0},\r
+ /* char 4 */ {0x0375, 0x06EA, 0x0DD4, 0x1BA8, 0x3750, 0x6EA0, 0xDD40},\r
+ /* char 5 */ {0xD849, 0xA0B3, 0x5147, 0xA28E, 0x553D, 0xAA7A, 0x44D5},\r
+ /* char 6 */ {0x6F45, 0xDE8A, 0xAD35, 0x4A4B, 0x9496, 0x390D, 0x721A},\r
+ /* char 7 */ {0xEB23, 0xC667, 0x9CEF, 0x29FF, 0x53FE, 0xA7FC, 0x5FD9},\r
+ /* char 8 */ {0x47D3, 0x8FA6, 0x0F6D, 0x1EDA, 0x3DB4, 0x7B68, 0xF6D0},\r
+ /* char 9 */ {0xB861, 0x60E3, 0xC1C6, 0x93AD, 0x377B, 0x6EF6, 0xDDEC},\r
+ /* char 10 */ {0x45A0, 0x8B40, 0x06A1, 0x0D42, 0x1A84, 0x3508, 0x6A10},\r
+ /* char 11 */ {0xAA51, 0x4483, 0x8906, 0x022D, 0x045A, 0x08B4, 0x1168},\r
+ /* char 12 */ {0x76B4, 0xED68, 0xCAF1, 0x85C3, 0x1BA7, 0x374E, 0x6E9C},\r
+ /* char 13 */ {0x3730, 0x6E60, 0xDCC0, 0xA9A1, 0x4363, 0x86C6, 0x1DAD},\r
+ /* char 14 */ {0x3331, 0x6662, 0xCCC4, 0x89A9, 0x0373, 0x06E6, 0x0DCC},\r
+ /* char 15 */ {0x1021, 0x2042, 0x4084, 0x8108, 0x1231, 0x2462, 0x48C4}\r
+ };\r
+\r
+ /**\r
+ * This method generates the xored-hashed password for word documents < 2007.\r
+ * Its output will be used as password input for the newer word generations which\r
+ * utilize a real hashing algorithm like sha1.\r
+ * \r
+ * Although the code was taken from the "see"-link below, this looks similar\r
+ * to the method in [MS-OFFCRYPTO] 2.3.7.2 Binary Document XOR Array Initialization Method 1. \r
+ *\r
+ * @param password\r
+ * @return the hashed password\r
+ * \r
+ * @see <a href="http://blogs.msdn.com/b/vsod/archive/2010/04/05/how-to-set-the-editing-restrictions-in-word-using-open-xml-sdk-2-0.aspx">How to set the editing restrictions in Word using Open XML SDK 2.0</a>\r
+ * @see <a href="http://www.aspose.com/blogs/aspose-blogs/vladimir-averkin/archive/2007/08/20/funny-how-the-new-powerful-cryptography-implemented-in-word-2007-turns-it-into-a-perfect-tool-for-document-password-removal.html">Funny: How the new powerful cryptography implemented in Word 2007 turns it into a perfect tool for document password removal.</a>\r
+ */\r
+ public static int xorHashPasswordAsInt(String password) {\r
+ //Array to hold Key Values\r
+ byte[] generatedKey = new byte[4];\r
+\r
+ //Maximum length of the password is 15 chars.\r
+ final int intMaxPasswordLength = 15; \r
+ \r
+ if (!"".equals(password)) {\r
+ // Truncate the password to 15 characters\r
+ password = password.substring(0, Math.min(password.length(), intMaxPasswordLength));\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
+ // --> For each character, if the low byte is not equal to 0, take it. Otherwise, take the high byte.\r
+ byte[] arrByteChars = new byte[password.length()];\r
+ \r
+ for (int i = 0; i < password.length(); i++) {\r
+ int intTemp = password.charAt(i);\r
+ byte lowByte = (byte)(intTemp & 0x00FF);\r
+ byte highByte = (byte)((intTemp & 0xFF00) >> 8);\r
+ arrByteChars[i] = (lowByte != 0 ? lowByte : highByte);\r
+ }\r
+\r
+ // Compute the high-order word of the new key:\r
+\r
+ // --> Initialize from the initial code array (see below), depending on the passwords length. \r
+ int highOrderWord = InitialCodeArray[arrByteChars.length - 1];\r
+\r
+ // --> For each character in the password:\r
+ // --> For every bit in the character, starting with the least significant and progressing to (but excluding) \r
+ // 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
+ for (int intBit = 0; intBit < 7; intBit++) {\r
+ if ((arrByteChars[i] & (0x0001 << intBit)) != 0) {\r
+ highOrderWord ^= EncryptionMatrix[tmp][intBit];\r
+ }\r
+ }\r
+ }\r
+ \r
+ // Compute the low-order word of the new key:\r
+ \r
+ // Initialize with 0\r
+ int lowOrderWord = 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
+ }\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
+\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, 2, (short)highOrderWord);\r
+ }\r
+ \r
+ return LittleEndian.getInt(generatedKey);\r
+ }\r
+\r
+ /**\r
+ * This method generates the xored-hashed password for word documents < 2007.\r
+ */\r
+ public static String xorHashPassword(String password) {\r
+ int hashedPassword = xorHashPasswordAsInt(password);\r
+ return String.format("%1$08X", hashedPassword);\r
+ }\r
+ \r
+ /**\r
+ * Convenience function which returns the reversed xored-hashed password for further \r
+ * processing in word documents 2007 and newer, which utilize a real hashing algorithm like sha1.\r
+ */\r
+ public static String xorHashPasswordReversed(String password) {\r
+ int hashedPassword = xorHashPasswordAsInt(password);\r
+ \r
+ return String.format("%1$02X%2$02X%3$02X%4$02X"\r
+ , ( hashedPassword >>> 0 ) & 0xFF\r
+ , ( hashedPassword >>> 8 ) & 0xFF\r
+ , ( hashedPassword >>> 16 ) & 0xFF\r
+ , ( hashedPassword >>> 24 ) & 0xFF\r
+ );\r
+ }\r
}\r
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.util.IOUtils;
import org.apache.poi.util.IdentifierManager;
import org.apache.poi.util.Internal;
settings.setEnforcementEditValue(STDocProtect.READ_ONLY);
}
+ /**
+ * Enforces the readOnly protection with a password.<br/>
+ * <br/>
+ * sample snippet from settings.xml
+ * <pre>
+ * <w:documentProtection w:edit="readOnly" w:enforcement="1"
+ * w:cryptProviderType="rsaAES" w:cryptAlgorithmClass="hash"
+ * w:cryptAlgorithmType="typeAny" w:cryptAlgorithmSid="14"
+ * w:cryptSpinCount="100000" w:hash="..." w:salt="...."
+ * />
+ * </pre>
+ *
+ * @param password the plaintext password, if null no password will be applied
+ * @param hashAlgo the hash algorithm - only md2, m5, sha1, sha256, sha384 and sha512 are supported.
+ * if null, it will default default to sha1
+ */
+ public void enforceReadonlyProtection(String password, HashAlgorithm hashAlgo) {
+ settings.setEnforcementEditValue(STDocProtect.READ_ONLY, password, hashAlgo);
+ }
+
/**
* Enforce the Filling Forms protection.<br/>
* In the documentProtection tag inside settings.xml file, <br/>
settings.setEnforcementEditValue(STDocProtect.FORMS);
}
+ /**
+ * Enforce the Filling Forms protection.<br/>
+ * <br/>
+ * sample snippet from settings.xml
+ * <pre>
+ * <w:documentProtection w:edit="forms" w:enforcement="1"
+ * w:cryptProviderType="rsaAES" w:cryptAlgorithmClass="hash"
+ * w:cryptAlgorithmType="typeAny" w:cryptAlgorithmSid="14"
+ * w:cryptSpinCount="100000" w:hash="..." w:salt="...."
+ * />
+ * </pre>
+ *
+ * @param password the plaintext password, if null no password will be applied
+ * @param hashAlgo the hash algorithm - only md2, m5, sha1, sha256, sha384 and sha512 are supported.
+ * if null, it will default default to sha1
+ */
+ public void enforceFillingFormsProtection(String password, HashAlgorithm hashAlgo) {
+ settings.setEnforcementEditValue(STDocProtect.FORMS, password, hashAlgo);
+ }
+
/**
* Enforce the Comments protection.<br/>
* In the documentProtection tag inside settings.xml file,<br/>
settings.setEnforcementEditValue(STDocProtect.COMMENTS);
}
+ /**
+ * Enforce the Comments protection.<br/>
+ * <br/>
+ * sample snippet from settings.xml
+ * <pre>
+ * <w:documentProtection w:edit="comments" w:enforcement="1"
+ * w:cryptProviderType="rsaAES" w:cryptAlgorithmClass="hash"
+ * w:cryptAlgorithmType="typeAny" w:cryptAlgorithmSid="14"
+ * w:cryptSpinCount="100000" w:hash="..." w:salt="...."
+ * />
+ * </pre>
+ *
+ * @param password the plaintext password, if null no password will be applied
+ * @param hashAlgo the hash algorithm - only md2, m5, sha1, sha256, sha384 and sha512 are supported.
+ * if null, it will default default to sha1
+ */
+ public void enforceCommentsProtection(String password, HashAlgorithm hashAlgo) {
+ settings.setEnforcementEditValue(STDocProtect.COMMENTS, password, hashAlgo);
+ }
+
/**
* Enforce the Tracked Changes protection.<br/>
* In the documentProtection tag inside settings.xml file, <br/>
settings.setEnforcementEditValue(STDocProtect.TRACKED_CHANGES);
}
+ /**
+ * Enforce the Tracked Changes protection.<br/>
+ * <br/>
+ * sample snippet from settings.xml
+ * <pre>
+ * <w:documentProtection w:edit="trackedChanges" w:enforcement="1"
+ * w:cryptProviderType="rsaAES" w:cryptAlgorithmClass="hash"
+ * w:cryptAlgorithmType="typeAny" w:cryptAlgorithmSid="14"
+ * w:cryptSpinCount="100000" w:hash="..." w:salt="...."
+ * />
+ * </pre>
+ *
+ * @param password the plaintext password, if null no password will be applied
+ * @param hashAlgo the hash algorithm - only md2, m5, sha1, sha256, sha384 and sha512 are supported.
+ * if null, it will default default to sha1
+ */
+ public void enforceTrackedChangesProtection(String password, HashAlgorithm hashAlgo) {
+ settings.setEnforcementEditValue(STDocProtect.TRACKED_CHANGES, password, hashAlgo);
+ }
+
+ /**
+ * Validates the existing password
+ *
+ * @param password
+ * @return true, only if password was set and equals, false otherwise
+ */
+ public boolean validateProtectionPassword(String password) {
+ return settings.validateProtectionPassword(password);
+ }
+
/**
* Remove protection enforcement.<br/>
* In the documentProtection tag inside settings.xml file <br/>
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
+import java.security.SecureRandom;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import javax.xml.namespace.QName;
+import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.POIXMLDocumentPart;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.openxml4j.opc.PackageRelationship;
+import org.apache.poi.poifs.crypt.CryptoFunctions;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.xmlbeans.XmlOptions;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDocProtect;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTOnOff;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSettings;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTZoom;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.STAlgClass;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.STAlgType;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.STCryptProv;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STDocProtect;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STOnOff;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.SettingsDocument;
safeGetDocumentProtection().setEdit(editValue);
}
+ /**
+ * Enforces the protection with the option specified by passed editValue and password.<br/>
+ * <br/>
+ * sample snippet from settings.xml
+ * <pre>
+ * <w:documentProtection w:edit="[passed editValue]" w:enforcement="1"
+ * w:cryptProviderType="rsaAES" w:cryptAlgorithmClass="hash"
+ * w:cryptAlgorithmType="typeAny" w:cryptAlgorithmSid="14"
+ * w:cryptSpinCount="100000" w:hash="..." w:salt="...."
+ * />
+ * </pre>
+ *
+ * @param editValue the protection type
+ * @param password the plaintext password, if null no password will be applied
+ * @param hashAlgo the hash algorithm - only md2, m5, sha1, sha256, sha384 and sha512 are supported.
+ * if null, it will default default to sha1
+ */
+ public void setEnforcementEditValue(org.openxmlformats.schemas.wordprocessingml.x2006.main.STDocProtect.Enum editValue,
+ String password, HashAlgorithm hashAlgo) {
+ safeGetDocumentProtection().setEnforcement(STOnOff.X_1);
+ safeGetDocumentProtection().setEdit(editValue);
+
+ if (password == null) {
+ if (safeGetDocumentProtection().isSetCryptProviderType()) {
+ safeGetDocumentProtection().unsetCryptProviderType();
+ }
+
+ if (safeGetDocumentProtection().isSetCryptAlgorithmClass()) {
+ safeGetDocumentProtection().unsetCryptAlgorithmClass();
+ }
+
+ if (safeGetDocumentProtection().isSetCryptAlgorithmType()) {
+ safeGetDocumentProtection().unsetCryptAlgorithmType();
+ }
+
+ if (safeGetDocumentProtection().isSetCryptAlgorithmSid()) {
+ safeGetDocumentProtection().unsetCryptAlgorithmSid();
+ }
+
+ if (safeGetDocumentProtection().isSetSalt()) {
+ safeGetDocumentProtection().unsetSalt();
+ }
+
+ if (safeGetDocumentProtection().isSetCryptSpinCount()) {
+ safeGetDocumentProtection().unsetCryptSpinCount();
+ }
+
+ if (safeGetDocumentProtection().isSetHash()) {
+ safeGetDocumentProtection().unsetHash();
+ }
+ } else {
+ final STCryptProv.Enum providerType;
+ final int sid;
+ switch (hashAlgo) {
+ case md2:
+ providerType = STCryptProv.RSA_FULL;
+ sid = 1;
+ break;
+ // md4 is not supported by JCE
+ case md5:
+ providerType = STCryptProv.RSA_FULL;
+ sid = 3;
+ break;
+ case sha1:
+ providerType = STCryptProv.RSA_FULL;
+ sid = 4;
+ break;
+ case sha256:
+ providerType = STCryptProv.RSA_AES;
+ sid = 12;
+ break;
+ case sha384:
+ providerType = STCryptProv.RSA_AES;
+ sid = 13;
+ break;
+ case sha512:
+ providerType = STCryptProv.RSA_AES;
+ sid = 14;
+ break;
+ default:
+ throw new EncryptedDocumentException
+ ("Hash algorithm '"+hashAlgo+"' is not supported for document write protection.");
+ }
+
+
+ SecureRandom random = new SecureRandom();
+ byte salt[] = random.generateSeed(16);
+
+ // Iterations specifies the number of times the hashing function shall be iteratively run (using each
+ // iteration's result as the input for the next iteration).
+ int spinCount = 100000;
+
+ if (hashAlgo == null) hashAlgo = HashAlgorithm.sha1;
+
+ String legacyHash = CryptoFunctions.xorHashPasswordReversed(password);
+ // Implementation Notes List:
+ // --> In this third stage, the reversed byte order legacy hash from the second stage shall
+ // be converted to Unicode hex string representation
+ byte hash[] = CryptoFunctions.hashPassword(legacyHash, hashAlgo, salt, spinCount, false);
+
+ safeGetDocumentProtection().setSalt(salt);
+ safeGetDocumentProtection().setHash(hash);
+ safeGetDocumentProtection().setCryptSpinCount(BigInteger.valueOf(spinCount));
+ safeGetDocumentProtection().setCryptAlgorithmType(STAlgType.TYPE_ANY);
+ safeGetDocumentProtection().setCryptAlgorithmClass(STAlgClass.HASH);
+ safeGetDocumentProtection().setCryptProviderType(providerType);
+ safeGetDocumentProtection().setCryptAlgorithmSid(BigInteger.valueOf(sid));
+ }
+ }
+
+ /**
+ * Validates the existing password
+ *
+ * @param password
+ * @return true, only if password was set and equals, false otherwise
+ */
+ public boolean validateProtectionPassword(String password) {
+ BigInteger sid = safeGetDocumentProtection().getCryptAlgorithmSid();
+ byte hash[] = safeGetDocumentProtection().getHash();
+ byte salt[] = safeGetDocumentProtection().getSalt();
+ BigInteger spinCount = safeGetDocumentProtection().getCryptSpinCount();
+
+ if (sid == null || hash == null || salt == null || spinCount == null) return false;
+
+ HashAlgorithm hashAlgo;
+ switch (sid.intValue()) {
+ case 1: hashAlgo = HashAlgorithm.md2; break;
+ case 3: hashAlgo = HashAlgorithm.md5; break;
+ case 4: hashAlgo = HashAlgorithm.sha1; break;
+ case 12: hashAlgo = HashAlgorithm.sha256; break;
+ case 13: hashAlgo = HashAlgorithm.sha384; break;
+ case 14: hashAlgo = HashAlgorithm.sha512; break;
+ default: return false;
+ }
+
+ String legacyHash = CryptoFunctions.xorHashPasswordReversed(password);
+ // Implementation Notes List:
+ // --> In this third stage, the reversed byte order legacy hash from the second stage shall
+ // be converted to Unicode hex string representation
+ byte hash2[] = CryptoFunctions.hashPassword(legacyHash, hashAlgo, salt, spinCount.intValue(), false);
+
+ return Arrays.equals(hash, hash2);
+ }
+
/**
* Removes protection enforcement.<br/>
* In the documentProtection tag inside settings.xml file <br/>
throw new RuntimeException(e);
}
}
-
}
==================================================================== */
package org.apache.poi.xwpf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
-import junit.framework.TestCase;
-
+import org.apache.poi.poifs.crypt.CryptoFunctions;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.util.TempFile;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
+import org.junit.Test;
-public class TestDocumentProtection extends TestCase {
+public class TestDocumentProtection {
+ @Test
public void testShouldReadEnforcementProperties() throws Exception {
XWPFDocument documentWithoutDocumentProtectionTag = XWPFTestDataSamples.openSampleDocument("documentProtection_no_protection.docx");
}
+ @Test
public void testShouldEnforceForReadOnly() throws Exception {
// XWPFDocument document = createDocumentFromSampleFile("test-data/document/documentProtection_no_protection.docx");
XWPFDocument document = XWPFTestDataSamples.openSampleDocument("documentProtection_no_protection.docx");
assertTrue(document.isEnforcedReadonlyProtection());
}
+ @Test
public void testShouldEnforceForFillingForms() throws Exception {
XWPFDocument document = XWPFTestDataSamples.openSampleDocument("documentProtection_no_protection.docx");
assertFalse(document.isEnforcedFillingFormsProtection());
assertTrue(document.isEnforcedFillingFormsProtection());
}
+ @Test
public void testShouldEnforceForComments() throws Exception {
XWPFDocument document = XWPFTestDataSamples.openSampleDocument("documentProtection_no_protection.docx");
assertFalse(document.isEnforcedCommentsProtection());
assertTrue(document.isEnforcedCommentsProtection());
}
+ @Test
public void testShouldEnforceForTrackedChanges() throws Exception {
XWPFDocument document = XWPFTestDataSamples.openSampleDocument("documentProtection_no_protection.docx");
assertFalse(document.isEnforcedTrackedChangesProtection());
assertTrue(document.isEnforcedTrackedChangesProtection());
}
+ @Test
public void testShouldUnsetEnforcement() throws Exception {
XWPFDocument document = XWPFTestDataSamples.openSampleDocument("documentProtection_readonly_no_password.docx");
assertTrue(document.isEnforcedReadonlyProtection());
assertFalse(document.isEnforcedReadonlyProtection());
}
+ @Test
public void testIntegration() throws Exception {
XWPFDocument doc = new XWPFDocument();
assertTrue(document.isEnforcedCommentsProtection());
}
+ @Test
public void testUpdateFields() throws Exception {
XWPFDocument doc = new XWPFDocument();
assertFalse(doc.isEnforcedUpdateFields());
assertTrue(doc.isEnforcedUpdateFields());
}
+ @Test
+ public void bug56076_read() throws Exception {
+ // test legacy xored-hashed password
+ assertEquals("64CEED7E", CryptoFunctions.xorHashPassword("Example"));
+ // check leading 0
+ assertEquals("0005CB00", CryptoFunctions.xorHashPassword("34579"));
+
+ // test document write protection with password
+ XWPFDocument document = XWPFTestDataSamples.openSampleDocument("bug56076.docx");
+ boolean isValid = document.validateProtectionPassword("Example");
+ assertTrue(isValid);
+ }
+
+ @Test
+ public void bug56076_write() throws Exception {
+ // test document write protection with password
+ XWPFDocument document = new XWPFDocument();
+ document.enforceCommentsProtection("Example", HashAlgorithm.sha512);
+ document = XWPFTestDataSamples.writeOutAndReadBack(document);
+ boolean isValid = document.validateProtectionPassword("Example");
+ assertTrue(isValid);
+ }
}