diff options
author | Andreas Beeker <kiwiwings@apache.org> | 2014-02-21 23:19:57 +0000 |
---|---|---|
committer | Andreas Beeker <kiwiwings@apache.org> | 2014-02-21 23:19:57 +0000 |
commit | fd8ad223a74c4cb53e238c2af656bf4f87369835 (patch) | |
tree | 0b4adc399144fb27925730615d590e9855e6e5b2 /src/ooxml/java/org/apache/poi/xwpf | |
parent | 7767a2414e0328bf87922e035560e97a6922eaf8 (diff) | |
download | poi-fd8ad223a74c4cb53e238c2af656bf4f87369835.tar.gz poi-fd8ad223a74c4cb53e238c2af656bf4f87369835.zip |
Bug 56076 - Add document protection with password support to XWPF
Bug 56077 - Add password hash function to HWPF
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1570750 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/ooxml/java/org/apache/poi/xwpf')
-rw-r--r-- | src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java | 91 | ||||
-rw-r--r-- | src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFSettings.java | 153 |
2 files changed, 243 insertions, 1 deletions
diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java index 978e69d351..65952cbae9 100644 --- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java +++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java @@ -47,6 +47,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.util.IOUtils; import org.apache.poi.util.IdentifierManager; import org.apache.poi.util.Internal; @@ -994,6 +995,26 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody { } /** + * 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/> * it sets the value of enforcement to "1" (w:enforcement="1") <br/> @@ -1010,6 +1031,26 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody { } /** + * 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/> * it sets the value of enforcement to "1" (w:enforcement="1") <br/> @@ -1026,6 +1067,26 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody { } /** + * 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/> * it sets the value of enforcement to "1" (w:enforcement="1") <br/> @@ -1042,6 +1103,36 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody { } /** + * 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/> * it sets the value of enforcement to "0" (w:enforcement="0") <br/> diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFSettings.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFSettings.java index 8ceddbe35e..1f521621b6 100644 --- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFSettings.java +++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFSettings.java @@ -20,19 +20,27 @@ import java.io.IOException; 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; @@ -140,6 +148,150 @@ public class XWPFSettings extends POIXMLDocumentPart { } /** + * 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/> * it sets the value of enforcement to "0" (w:enforcement="0") <br/> @@ -204,5 +356,4 @@ public class XWPFSettings extends POIXMLDocumentPart { throw new RuntimeException(e); } } - } |