<test name="org.apache.fop.text.linebreak.LineBreakStatusTest" todir="${junit.reports.dir}"/>
</junit>
</target>
- <target name="junit-reduced" depends="junit-userconfig, junit-basic, junit-transcoder, junit-text-linebreak, junit-fotree"/>
+ <target name="junit-render-pdf" depends="junit-compile">
+ <echo message="Running tests for the render pdf package"/>
+ <junit-run title="render-pdf" testsuite="org.apache.fop.render.pdf.RenderPDFTestSuite"
+ outfile="TEST-render-pdf"/>
+ </target>
+ <target name="junit-reduced" depends="junit-userconfig, junit-basic, junit-transcoder,
+ junit-text-linebreak, junit-fotree, junit-render-pdf"/>
<target name="junit-full" depends="junit-reduced, junit-layout, junit-area-tree-xml-format, junit-intermediate-format"/>
<target name="junit" depends="junit-full" description="Runs all of FOP's JUnit tests" if="junit.present">
<fail><condition><or><isset property="fop.junit.error"/><isset property="fop.junit.failure"/><not><isset property="hyphenation.present"/></not></or></condition>
<source><![CDATA[
<renderer mime="application/pdf">
<encryption-params>
+ <encryption-length>128</encryption-length>
<user-password>testuserpass</user-password>
<owner-password>testownerpass</owner-password>
<noprint/>
<nocopy/>
<noedit/>
<noannotations/>
+ <nofillinforms/>
+ <noaccesscontent/>
+ <noassembledoc/>
+ <noprinthq/>
</encryption-params>
</renderer>]]></source>
supplied, viewing the content is not restricted.
</p>
<p>
- Further restrictions can be imposed by using the <code>-noprint</code>,
- <code>-nocopy</code>, <code>-noedit</code> and
- <code>-noannotations</code> options, which disable printing, copying
- text, editing in Adobe Acrobat and making annotations, respectively.
+ Further restrictions can be imposed by using the following command-line options:
+ <table>
+ <tr>
+ <th>Option</th>
+ <th>Description</th>
+ </tr>
+ <tr>
+ <td><code>-noprint</code></td>
+ <td>disable printing</td>
+ </tr>
+ <tr>
+ <td><code>-nocopy</code></td>
+ <td>disable copy/paste of content</td>
+ </tr>
+ <tr>
+ <td><code>-noedit</code></td>
+ <td>disable editing in Adobe Acrobat</td>
+ </tr>
+ <tr>
+ <td><code>-noannotations</code></td>
+ <td>disable editing of annotations</td>
+ </tr>
+ <tr>
+ <td><code>-nofillinforms</code></td>
+ <td>disable filling in forms</td>
+ </tr>
+ <tr>
+ <td><code>-noaccesscontent</code></td>
+ <td>disable text and graphics extraction for accessibility purposes</td>
+ </tr>
+ <tr>
+ <td><code>-noassembledoc</code></td>
+ <td>disable assembling documents</td>
+ </tr>
+ <tr>
+ <td><code>-noprinthq</code></td>
+ <td>disable high quality printing</td>
+ </tr>
+ </table>
</p>
</section>
<section>
<th>Values</th>
<th>Default</th>
</tr>
+ <tr>
+ <td>encryption-length</td>
+ <td>The encryption length in bit</td>
+ <td>Any multiple of 8 between 40 and 128</td>
+ <td>40</td>
+ </tr>
<tr>
<td>ownerPassword</td>
<td>The owner password</td>
</tr>
<tr>
<td>allowEditContent</td>
- <td>Allows/disallows editing of content</td>
+ <td>Allows/disallows editing in Adobe Acrobat</td>
<td>"TRUE" or "FALSE"</td>
<td>"TRUE"</td>
</tr>
<td>"TRUE" or "FALSE"</td>
<td>"TRUE"</td>
</tr>
+ <tr>
+ <td>allowFillInForms</td>
+ <td>Allows/disallows filling in forms</td>
+ <td>"TRUE" or "FALSE"</td>
+ <td>"TRUE"</td>
+ </tr>
+ <tr>
+ <td>allowAccessContent</td>
+ <td>Allows/disallows text and graphics extraction for accessibility purposes</td>
+ <td>"TRUE" or "FALSE"</td>
+ <td>"TRUE"</td>
+ </tr>
+ <tr>
+ <td>allowAssembleDocument</td>
+ <td>Allows/disallows assembling document</td>
+ <td>"TRUE" or "FALSE"</td>
+ <td>"TRUE"</td>
+ </tr>
+ <tr>
+ <td>allowPrintHq</td>
+ <td>Allows/disallows high quality printing</td>
+ <td>"TRUE" or "FALSE"</td>
+ <td>"TRUE"</td>
+ </tr>
</table>
<note>
Encryption is enabled as soon as one of these options is set.
<li>allowCopyContent: true if copying content is allowed</li>
<li>allowEditContent: true if editing content is allowed</li>
<li>allowEditAnnotations: true if editing annotations is allowed</li>
+ <li>allowFillInForms: true if filling in forms is allowed.</li>
+ <li>allowAccessContent: true if extracting text and graphics is allowed</li>
+ <li>allowAssembleDocument: true if assembling document is allowed</li>
+ <li>allowPrintHq: true if printing to high quality is allowed</li>
</ol>
<p>
Alternatively, you can set each value separately in the Map provided by
<li>nocopy: Boolean or "true"/"false"</li>
<li>noedit: Boolean or "true"/"false"</li>
<li>noannotations: Boolean or "true"/"false"</li>
+ <li>nofillinforms: Boolean or "true"/"false"</li>
+ <li>noaccesscontent: Boolean or "true"/"false"</li>
+ <li>noassembledoc: Boolean or "true"/"false"</li>
+ <li>noprinthq: Boolean or "true"/"false"</li>
</ol>
</section>
<section>
-nocopy PDF file will be encrypted without copy content permission
-noedit PDF file will be encrypted without edit content permission
-noannotations PDF file will be encrypted without edit annotation permission
+ -nofillinforms PDF file will be encrypted without fill in forms permission
+ -noaccesscontent PDF file will be encrypted without extract text and graphics permission
+ -noassembledoc PDF file will be encrypted without assemble the document permission
+ -noprinthq PDF file will be encrypted without print high quality permission
-a enables accessibility features (Tagged PDF etc., default off)
-pdfprofile prof PDF file will be generated with the specified profile
(Examples for prof: PDF/A-1b or PDF/X-3:2003)
getPDFEncryptionParams().setAllowEditContent(false);
} else if (args[i].equals("-noannotations")) {
getPDFEncryptionParams().setAllowEditAnnotations(false);
+ } else if (args[i].equals("-nofillinforms")) {
+ getPDFEncryptionParams().setAllowFillInForms(false);
+ } else if (args[i].equals("-noaccesscontent")) {
+ getPDFEncryptionParams().setAllowAccessContent(false);
+ } else if (args[i].equals("-noassembledoc")) {
+ getPDFEncryptionParams().setAllowAssembleDocument(false);
+ } else if (args[i].equals("-noprinthq")) {
+ getPDFEncryptionParams().setAllowPrintHq(false);
} else if (args[i].equals("-version")) {
printVersion();
return false;
+ " -nocopy PDF file will be encrypted without copy content permission\n"
+ " -noedit PDF file will be encrypted without edit content permission\n"
+ " -noannotations PDF file will be encrypted without edit annotation permission\n"
+ + " -nofillinforms PDF file will be encrypted without"
+ + " fill in interactive form fields permission\n"
+ + " -noaccesscontent PDF file will be encrypted without"
+ + " extract text and graphics permission\n"
+ + " -noassembledoc PDF file will be encrypted without"
+ + " assemble the document permission\n"
+ + " -noprinthq PDF file will be encrypted without"
+ + " print high quality permission\n"
+ " -a enables accessibility features (Tagged PDF etc., default off)\n"
+ " -pdfprofile prof PDF file will be generated with the specified profile\n"
+ " (Examples for prof: PDF/A-1b or PDF/X-3:2003)\n\n"
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Random;
+
+/**
+ * A class to generate the File Identifier of a PDF document (the ID entry of the file
+ * trailer dictionary).
+ */
+abstract class FileIDGenerator {
+
+ abstract byte[] getOriginalFileID();
+
+ abstract byte[] getUpdatedFileID();
+
+ private static final class RandomFileIDGenerator extends FileIDGenerator {
+
+ private byte[] fileID;
+
+ private RandomFileIDGenerator() {
+ Random random = new Random();
+ fileID = new byte[16];
+ random.nextBytes(fileID);
+ }
+
+ @Override
+ byte[] getOriginalFileID() {
+ return fileID;
+ }
+
+ @Override
+ byte[] getUpdatedFileID() {
+ return fileID;
+ }
+
+ }
+
+ private static final class DigestFileIDGenerator extends FileIDGenerator {
+
+ private byte[] fileID;
+
+ private final PDFDocument document;
+
+ private final MessageDigest digest;
+
+ DigestFileIDGenerator(PDFDocument document) throws NoSuchAlgorithmException {
+ this.document = document;
+ this.digest = MessageDigest.getInstance("MD5");
+ }
+
+ @Override
+ byte[] getOriginalFileID() {
+ if (fileID == null) {
+ generateFileID();
+ }
+ return fileID;
+ }
+
+ @Override
+ byte[] getUpdatedFileID() {
+ return getOriginalFileID();
+ }
+
+ private void generateFileID() {
+ DateFormat df = new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS");
+ digest.update(PDFDocument.encode(df.format(new Date())));
+ // Ignoring the filename here for simplicity even though it's recommended
+ // by the PDF spec
+ digest.update(PDFDocument.encode(String.valueOf(document.getCurrentFileSize())));
+ digest.update(document.getInfo().toPDF());
+ fileID = digest.digest();
+ }
+
+ }
+
+ /**
+ * Use this method when the file ID is needed before the document is finalized. The
+ * digest method recommended by the PDF Reference is based, among other things, on the
+ * file size.
+ *
+ * @return an instance that generates a random sequence of bytes for the File
+ * Identifier
+ */
+ static FileIDGenerator getRandomFileIDGenerator() {
+ return new RandomFileIDGenerator();
+ }
+
+ /**
+ * Returns an instance that generates a file ID using the digest method recommended by
+ * the PDF Reference. To properly follow the Reference, the size of the document must
+ * no longer change after this method is called.
+ *
+ * @param document the document whose File Identifier must be generated
+ * @return the generator
+ * @throws NoSuchAlgorithmException if the MD5 Digest algorithm is not available
+ */
+ static FileIDGenerator getDigestFileIDGenerator(PDFDocument document)
+ throws NoSuchAlgorithmException {
+ return new DigestFileIDGenerator(document);
+ }
+}
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
-import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
private boolean encodingOnTheFly = true;
+ private FileIDGenerator fileIDGenerator;
+
/**
* Creates an empty PDF document.
*
*/
public void setEncryption(PDFEncryptionParams params) {
getProfile().verifyEncryptionAllowed();
- this.encryption = PDFEncryptionManager.newInstance(++this.objectcount, params);
+ fileIDGenerator = FileIDGenerator.getRandomFileIDGenerator();
+ this.encryption = PDFEncryptionManager.newInstance(++this.objectcount, params, this);
if (this.encryption != null) {
PDFObject pdfObject = (PDFObject)this.encryption;
- pdfObject.setDocument(this);
addTrailerObject(pdfObject);
} else {
log.warn(
this.position += bin.length;
}
- /** @return the "ID" entry for the file trailer */
- protected String getIDEntry() {
- try {
- MessageDigest digest = MessageDigest.getInstance("MD5");
- DateFormat df = new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS");
- digest.update(encode(df.format(new Date())));
- //Ignoring the filename here for simplicity even though it's recommended by the PDF spec
- digest.update(encode(String.valueOf(this.position)));
- digest.update(getInfo().toPDF());
- byte[] res = digest.digest();
- String s = PDFText.toHex(res);
- return "/ID [" + s + " " + s + "]";
- } catch (NoSuchAlgorithmException e) {
- if (getProfile().isIDEntryRequired()) {
- throw new UnsupportedOperationException("MD5 not available: " + e.getMessage());
- } else {
- return ""; //Entry is optional if PDF/A or PDF/X are not active
- }
- }
- }
-
/**
* Write the trailer
*
if (this.isEncryptionActive()) {
pdf.append(this.encryption.getTrailerEntry());
} else {
- pdf.append(this.getIDEntry());
+ byte[] fileID = getFileIDGenerator().getOriginalFileID();
+ String fileIDAsString = PDFText.toHex(fileID);
+ pdf.append("/ID [" + fileIDAsString + " " + fileIDAsString + "]");
}
pdf.append("\n>>\nstartxref\n")
return pdfBytes.length;
}
+ long getCurrentFileSize() {
+ return position;
+ }
+
+ FileIDGenerator getFileIDGenerator() {
+ if (fileIDGenerator == null) {
+ try {
+ fileIDGenerator = FileIDGenerator.getDigestFileIDGenerator(this);
+ } catch (NoSuchAlgorithmException e) {
+ fileIDGenerator = FileIDGenerator.getRandomFileIDGenerator();
+ }
+ }
+ return fileIDGenerator;
+ }
}
*/
public interface PDFEncryption {
- /**
- * Returns the encryption parameters.
- * @return the encryption parameters
- */
- PDFEncryptionParams getParams();
-
- /**
- * Sets the encryption parameters.
- * @param params The parameterss to set
- */
- void setParams(PDFEncryptionParams params);
-
/**
* Adds a PDFFilter to the PDFStream object
* @param stream the stream to add an encryption filter to
package org.apache.fop.pdf;
-// Java
import java.io.IOException;
import java.io.OutputStream;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-import java.util.Random;
+import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
/**
- * class representing a /Filter /Standard object.
- *
+ * An implementation of the Standard Security Handler.
*/
-public class PDFEncryptionJCE extends PDFObject implements PDFEncryption {
+public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption {
- private class EncryptionFilter extends PDFFilter {
- private PDFEncryptionJCE encryption;
- private int number;
- private int generation;
+ private final MessageDigest digest;
- /**
- * The constructor for the internal PDFEncryptionJCE filter
- * @param encryption The encryption object to use
- * @param number The number of the object to be encrypted
- * @param generation The generation of the object to be encrypted
- */
- public EncryptionFilter(PDFEncryptionJCE encryption,
- int number, int generation) {
- super();
- this.encryption = encryption;
- this.number = number;
- this.generation = generation;
- log.debug("new encryption filter for number "
- + number + " and generation " + generation);
+ private byte[] encryptionKey;
+
+ private String encryptionDictionary;
+
+ private class EncryptionInitializer {
+
+ private final PDFEncryptionParams encryptionParams;
+
+ private int encryptionLength;
+
+ private int version;
+
+ private int revision;
+
+ EncryptionInitializer(PDFEncryptionParams params) {
+ this.encryptionParams = new PDFEncryptionParams(params);
}
+ void init() {
+ encryptionLength = encryptionParams.getEncryptionLengthInBits();
+ determineEncryptionAlgorithm();
+ int permissions = Permission.computePermissions(encryptionParams);
+ EncryptionSettings encryptionSettings = new EncryptionSettings(
+ encryptionLength, permissions,
+ encryptionParams.getUserPassword(), encryptionParams.getOwnerPassword());
+ InitializationEngine initializationEngine = (revision == 2)
+ ? new Rev2Engine(encryptionSettings)
+ : new Rev3Engine(encryptionSettings);
+ initializationEngine.run();
+ encryptionDictionary = createEncryptionDictionary(getObjectID(), permissions,
+ initializationEngine.oValue, initializationEngine.uValue);
+ }
+
+ private void determineEncryptionAlgorithm() {
+ if (isVersion1Revision2Algorithm()) {
+ version = 1;
+ revision = 2;
+ } else {
+ version = 2;
+ revision = 3;
+ }
+ }
+
+ private boolean isVersion1Revision2Algorithm() {
+ return encryptionLength == 40
+ && encryptionParams.isAllowFillInForms()
+ && encryptionParams.isAllowAccessContent()
+ && encryptionParams.isAllowAssembleDocument()
+ && encryptionParams.isAllowPrintHq();
+ }
+
+ private String createEncryptionDictionary(final String objectId, final int permissions,
+ final byte[] oValue, final byte[] uValue) {
+ return objectId
+ + "<< /Filter /Standard\n"
+ + "/V " + version + "\n"
+ + "/R " + revision + "\n"
+ + "/Length " + encryptionLength + "\n"
+ + "/P " + permissions + "\n"
+ + "/O " + PDFText.toHex(oValue) + "\n"
+ + "/U " + PDFText.toHex(uValue) + "\n"
+ + ">>\n"
+ + "endobj\n";
+ }
+
+ }
+
+ private static enum Permission {
+
+ PRINT(3),
+ EDIT_CONTENT(4),
+ COPY_CONTENT(5),
+ EDIT_ANNOTATIONS(6),
+ FILL_IN_FORMS(9),
+ ACCESS_CONTENT(10),
+ ASSEMBLE_DOCUMENT(11),
+ PRINT_HQ(12);
+
+ private final int mask;
+
/**
- * Return a PDF string representation of the filter. In this
- * case no filter name is passed.
- * @return The filter name, blank in this case
+ * Creates a new permission.
+ *
+ * @param bit bit position for this permission, 1-based to match the PDF Reference
*/
- public String getName() {
- return "";
+ private Permission(int bit) {
+ mask = 1 << (bit - 1);
+ }
+
+ private int removeFrom(int permissions) {
+ return permissions - mask;
+ }
+
+ static int computePermissions(PDFEncryptionParams encryptionParams) {
+ int permissions = -4;
+
+ if (!encryptionParams.isAllowPrint()) {
+ permissions = PRINT.removeFrom(permissions);
+ }
+ if (!encryptionParams.isAllowCopyContent()) {
+ permissions = COPY_CONTENT.removeFrom(permissions);
+ }
+ if (!encryptionParams.isAllowEditContent()) {
+ permissions = EDIT_CONTENT.removeFrom(permissions);
+ }
+ if (!encryptionParams.isAllowEditAnnotations()) {
+ permissions = EDIT_ANNOTATIONS.removeFrom(permissions);
+ }
+ if (!encryptionParams.isAllowFillInForms()) {
+ permissions = FILL_IN_FORMS.removeFrom(permissions);
+ }
+ if (!encryptionParams.isAllowAccessContent()) {
+ permissions = ACCESS_CONTENT.removeFrom(permissions);
+ }
+ if (!encryptionParams.isAllowAssembleDocument()) {
+ permissions = ASSEMBLE_DOCUMENT.removeFrom(permissions);
+ }
+ if (!encryptionParams.isAllowPrintHq()) {
+ permissions = PRINT_HQ.removeFrom(permissions);
+ }
+ return permissions;
+ }
+ }
+
+ private static final class EncryptionSettings {
+
+ final int encryptionLength; // CSOK: VisibilityModifier
+
+ final int permissions; // CSOK: VisibilityModifier
+
+ final String userPassword; // CSOK: VisibilityModifier
+
+ final String ownerPassword; // CSOK: VisibilityModifier
+
+ EncryptionSettings(int encryptionLength, int permissions,
+ String userPassword, String ownerPassword) {
+ this.encryptionLength = encryptionLength;
+ this.permissions = permissions;
+ this.userPassword = userPassword;
+ this.ownerPassword = ownerPassword;
+ }
+
+ }
+
+ private abstract class InitializationEngine {
+
+ /** Padding for passwords. */
+ protected final byte[] padding = new byte[] {
+ (byte) 0x28, (byte) 0xBF, (byte) 0x4E, (byte) 0x5E,
+ (byte) 0x4E, (byte) 0x75, (byte) 0x8A, (byte) 0x41,
+ (byte) 0x64, (byte) 0x00, (byte) 0x4E, (byte) 0x56,
+ (byte) 0xFF, (byte) 0xFA, (byte) 0x01, (byte) 0x08,
+ (byte) 0x2E, (byte) 0x2E, (byte) 0x00, (byte) 0xB6,
+ (byte) 0xD0, (byte) 0x68, (byte) 0x3E, (byte) 0x80,
+ (byte) 0x2F, (byte) 0x0C, (byte) 0xA9, (byte) 0xFE,
+ (byte) 0x64, (byte) 0x53, (byte) 0x69, (byte) 0x7A};
+
+ protected final int encryptionLengthInBytes;
+
+ private final int permissions;
+
+ private byte[] oValue;
+
+ private byte[] uValue;
+
+ private final byte[] preparedUserPassword;
+
+ protected final String ownerPassword;
+
+ InitializationEngine(EncryptionSettings encryptionSettings) {
+ this.encryptionLengthInBytes = encryptionSettings.encryptionLength / 8;
+ this.permissions = encryptionSettings.permissions;
+ this.preparedUserPassword = preparePassword(encryptionSettings.userPassword);
+ this.ownerPassword = encryptionSettings.ownerPassword;
+ }
+
+ void run() {
+ oValue = computeOValue();
+ createEncryptionKey();
+ uValue = computeUValue();
}
/**
- * Return a parameter dictionary for this filter, or null
- * @return The parameter dictionary. In this case, null.
+ * Applies Algorithm 3.3 Page 79 of the PDF 1.4 Reference.
+ *
+ * @return the O value
*/
- public PDFObject getDecodeParms() {
- return null;
+ private byte[] computeOValue() {
+ // Step 1
+ byte[] md5Input = prepareMD5Input();
+ // Step 2
+ digest.reset();
+ byte[] hash = digest.digest(md5Input);
+ // Step 3
+ hash = computeOValueStep3(hash);
+ // Step 4
+ byte[] key = new byte[encryptionLengthInBytes];
+ System.arraycopy(hash, 0, key, 0, encryptionLengthInBytes);
+ // Steps 5, 6
+ byte[] encryptionResult = encryptWithKey(key, preparedUserPassword);
+ // Step 7
+ encryptionResult = computeOValueStep7(key, encryptionResult);
+ // Step 8
+ return encryptionResult;
}
/**
- * {@inheritDoc}
+ * Applies Algorithm 3.2 Page 78 of the PDF 1.4 Reference.
*/
- public OutputStream applyFilter(OutputStream out) throws IOException {
- return new CipherOutputStream(out,
- encryption.initCipher(number, generation));
+ private void createEncryptionKey() {
+ // Steps 1, 2
+ digest.reset();
+ digest.update(preparedUserPassword);
+ // Step 3
+ digest.update(oValue);
+ // Step 4
+ digest.update((byte) (permissions >>> 0));
+ digest.update((byte) (permissions >>> 8));
+ digest.update((byte) (permissions >>> 16));
+ digest.update((byte) (permissions >>> 24));
+ // Step 5
+ digest.update(getDocumentSafely().getFileIDGenerator().getOriginalFileID());
+ byte[] hash = digest.digest();
+ // Step 6
+ hash = createEncryptionKeyStep6(hash);
+ // Step 7
+ encryptionKey = new byte[encryptionLengthInBytes];
+ System.arraycopy(hash, 0, encryptionKey, 0, encryptionLengthInBytes);
}
- }
+ protected abstract byte[] computeUValue();
- private static final char [] PAD
- = {0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
- 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
- 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
- 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A};
-
- /** Value of PRINT permission */
- public static final int PERMISSION_PRINT = 4;
- /** Value of content editting permission */
- public static final int PERMISSION_EDIT_CONTENT = 8;
- /** Value of content extraction permission */
- public static final int PERMISSION_COPY_CONTENT = 16;
- /** Value of annotation editting permission */
- public static final int PERMISSION_EDIT_ANNOTATIONS = 32;
-
- // Encryption tools
- private MessageDigest digest = null;
- //private Cipher cipher = null;
- private Random random = new Random();
- // Control attributes
- private PDFEncryptionParams params;
- // Output attributes
- private byte[] fileID = null;
- private byte[] encryptionKey = null;
- private String dictionary = null;
+ /**
+ * Adds padding to the password as directed in page 78 of the PDF 1.4 Reference.
+ *
+ * @param password the password
+ * @return the password with additional padding if necessary
+ */
+ private byte[] preparePassword(String password) {
+ int finalLength = 32;
+ byte[] preparedPassword = new byte[finalLength];
+ byte[] passwordBytes = password.getBytes();
+ System.arraycopy(passwordBytes, 0, preparedPassword, 0, passwordBytes.length);
+ System.arraycopy(padding, 0, preparedPassword, passwordBytes.length,
+ finalLength - passwordBytes.length);
+ return preparedPassword;
+ }
- /**
- * Create a /Filter /Standard object.
- *
- * @param objnum the object's number
- */
- public PDFEncryptionJCE(int objnum) {
- /* generic creation of object */
- super();
- setObjectNumber(objnum);
- try {
- digest = MessageDigest.getInstance("MD5");
- //cipher = Cipher.getInstance("RC4");
- } catch (NoSuchAlgorithmException e) {
- throw new UnsupportedOperationException(e.getMessage());
- /*} catch (NoSuchPaddingException e) {
- throw new UnsupportedOperationException(e.getMessage());*/
+ private byte[] prepareMD5Input() {
+ if (ownerPassword.length() != 0) {
+ return preparePassword(ownerPassword);
+ } else {
+ return preparedUserPassword;
+ }
}
- }
- /**
- * Local factory method.
- * @param objnum PDF object number for the encryption object
- * @param params PDF encryption parameters
- * @return PDFEncryption the newly created PDFEncryption object
- */
- public static PDFEncryption make(int objnum, PDFEncryptionParams params) {
- PDFEncryptionJCE impl = new PDFEncryptionJCE(objnum);
- impl.setParams(params);
- impl.init();
- return impl;
- }
+ protected abstract byte[] computeOValueStep3(byte[] hash);
+ protected abstract byte[] computeOValueStep7(byte[] key, byte[] encryptionResult);
- /**
- * Returns the encryption parameters.
- * @return the encryption parameters
- */
- public PDFEncryptionParams getParams() {
- return this.params;
- }
+ protected abstract byte[] createEncryptionKeyStep6(byte[] hash);
- /**
- * Sets the encryption parameters.
- * @param params The parameterss to set
- */
- public void setParams(PDFEncryptionParams params) {
- this.params = params;
}
- // Internal procedures
+ private class Rev2Engine extends InitializationEngine {
- private byte[] prepPassword(String password) {
- byte[] obuffer = new byte[32];
- byte[] pbuffer = password.getBytes();
+ Rev2Engine(EncryptionSettings encryptionSettings) {
+ super(encryptionSettings);
+ }
- int i = 0;
- int j = 0;
+ @Override
+ protected byte[] computeOValueStep3(byte[] hash) {
+ return hash;
+ }
- while (i < obuffer.length && i < pbuffer.length) {
- obuffer[i] = pbuffer[i];
- i++;
+ @Override
+ protected byte[] computeOValueStep7(byte[] key, byte[] encryptionResult) {
+ return encryptionResult;
}
- while (i < obuffer.length) {
- obuffer[i++] = (byte) PAD[j++];
+
+ @Override
+ protected byte[] createEncryptionKeyStep6(byte[] hash) {
+ return hash;
+ }
+
+ @Override
+ protected byte[] computeUValue() {
+ return encryptWithKey(encryptionKey, padding);
}
- return obuffer;
}
- /**
- * Returns the document file ID
- * @return The file ID
- */
- public byte[] getFileID() {
- if (fileID == null) {
- fileID = new byte[16];
- random.nextBytes(fileID);
+ private class Rev3Engine extends InitializationEngine {
+
+ Rev3Engine(EncryptionSettings encryptionSettings) {
+ super(encryptionSettings);
}
- return fileID;
- }
+ @Override
+ protected byte[] computeOValueStep3(byte[] hash) {
+ for (int i = 0; i < 50; i++) {
+ hash = digest.digest(hash);
+ }
+ return hash;
+ }
- /**
- * This method returns the indexed file ID
- * @param index The index to access the file ID
- * @return The file ID
- */
- public String getFileID(int index) {
- if (index == 1) {
- return PDFText.toHex(getFileID());
+ @Override
+ protected byte[] computeOValueStep7(byte[] key, byte[] encryptionResult) {
+ return xorKeyAndEncrypt19Times(key, encryptionResult);
}
- byte[] id = new byte[16];
- random.nextBytes(id);
- return PDFText.toHex(id);
- }
+ @Override
+ protected byte[] createEncryptionKeyStep6(byte[] hash) {
+ for (int i = 0; i < 50; i++) {
+ digest.update(hash, 0, encryptionLengthInBytes);
+ hash = digest.digest();
+ }
+ return hash;
+ }
- private byte[] encryptWithKey(byte[] data, byte[] key) {
- try {
- final Cipher c = initCipher(key);
- return c.doFinal(data);
- } catch (IllegalBlockSizeException e) {
- throw new IllegalStateException(e.getMessage());
- } catch (BadPaddingException e) {
- throw new IllegalStateException(e.getMessage());
+ @Override
+ protected byte[] computeUValue() {
+ // Step 1 is encryptionKey
+ // Step 2
+ digest.reset();
+ digest.update(padding);
+ // Step 3
+ digest.update(getDocumentSafely().getFileIDGenerator().getOriginalFileID());
+ // Step 4
+ byte[] encryptionResult = encryptWithKey(encryptionKey, digest.digest());
+ // Step 5
+ encryptionResult = xorKeyAndEncrypt19Times(encryptionKey, encryptionResult);
+ // Step 6
+ byte[] uValue = new byte[32];
+ System.arraycopy(encryptionResult, 0, uValue, 0, 16);
+ // Add the arbitrary padding
+ Arrays.fill(uValue, 16, 32, (byte) 0);
+ return uValue;
}
- }
- private Cipher initCipher(byte[] key) {
- try {
- Cipher c = Cipher.getInstance("RC4");
- SecretKeySpec keyspec = new SecretKeySpec(key, "RC4");
- c.init(Cipher.ENCRYPT_MODE, keyspec);
- return c;
- } catch (InvalidKeyException e) {
- throw new IllegalStateException(e.getMessage());
- } catch (NoSuchAlgorithmException e) {
- throw new UnsupportedOperationException(e.getMessage());
- } catch (NoSuchPaddingException e) {
- throw new UnsupportedOperationException(e.getMessage());
+ private byte[] xorKeyAndEncrypt19Times(byte[] key, byte[] input) {
+ byte[] result = input;
+ byte[] encryptionKey = new byte[key.length];
+ for (int i = 1; i <= 19; i++) {
+ for (int j = 0; j < key.length; j++) {
+ encryptionKey[j] = (byte) (key[j] ^ i);
+ }
+ result = encryptWithKey(encryptionKey, result);
+ }
+ return result;
}
- }
- private Cipher initCipher(int number, int generation) {
- byte[] hash = calcHash(number, generation);
- int size = hash.length;
- hash = digest.digest(hash);
- byte[] key = calcKey(hash, size);
- return initCipher(key);
}
- private byte[] encryptWithHash(byte[] data, byte[] hash, int size) {
- hash = digest.digest(hash);
+ private class EncryptionFilter extends PDFFilter {
- byte[] key = calcKey(hash, size);
+ private int streamNumber;
- return encryptWithKey(data, key);
- }
+ private int streamGeneration;
- private byte[] calcKey(byte[] hash, int size) {
- byte[] key = new byte[size];
+ EncryptionFilter(int streamNumber, int streamGeneration) {
+ this.streamNumber = streamNumber;
+ this.streamGeneration = streamGeneration;
+ }
- for (int i = 0; i < size; i++) {
- key[i] = hash[i];
+ /**
+ * Returns a PDF string representation of this filter.
+ *
+ * @return the empty string
+ */
+ public String getName() {
+ return "";
}
- return key;
+
+ /**
+ * Returns a parameter dictionary for this filter.
+ *
+ * @return null, this filter has no parameters
+ */
+ public PDFObject getDecodeParms() {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ public OutputStream applyFilter(OutputStream out) throws IOException {
+ byte[] key = createEncryptionKey(streamNumber, streamGeneration);
+ Cipher cipher = initCipher(key);
+ return new CipherOutputStream(out, cipher);
+ }
+
}
- /**
- * This method initializes the encryption algorithms and values
- */
- public void init() {
- // Generate the owner value
- byte[] oValue;
- if (params.getOwnerPassword().length() > 0) {
- oValue = encryptWithHash(
- prepPassword(params.getUserPassword()),
- prepPassword(params.getOwnerPassword()), 5);
- } else {
- oValue = encryptWithHash(
- prepPassword(params.getUserPassword()),
- prepPassword(params.getUserPassword()), 5);
- }
-
- // Generate permissions value
- int permissions = -4;
-
- if (!params.isAllowPrint()) {
- permissions -= PERMISSION_PRINT;
- }
- if (!params.isAllowCopyContent()) {
- permissions -= PERMISSION_COPY_CONTENT;
- }
- if (!params.isAllowEditContent()) {
- permissions -= PERMISSION_EDIT_CONTENT;
- }
- if (!params.isAllowEditAnnotations()) {
- permissions -= PERMISSION_EDIT_ANNOTATIONS;
- }
-
- // Create the encrption key
- digest.update(prepPassword(params.getUserPassword()));
- digest.update(oValue);
- digest.update((byte) (permissions >>> 0));
- digest.update((byte) (permissions >>> 8));
- digest.update((byte) (permissions >>> 16));
- digest.update((byte) (permissions >>> 24));
- digest.update(getFileID());
-
- byte [] hash = digest.digest();
- this.encryptionKey = new byte[5];
-
- for (int i = 0; i < 5; i++) {
- this.encryptionKey[i] = hash[i];
- }
-
- // Create the user value
- byte[] uValue = encryptWithKey(prepPassword(""), this.encryptionKey);
-
- // Create the dictionary
- this.dictionary = getObjectID()
- + "<< /Filter /Standard\n"
- + "/V 1\n"
- + "/R 2\n"
- + "/Length 40\n"
- + "/P " + permissions + "\n"
- + "/O " + PDFText.toHex(oValue) + "\n"
- + "/U " + PDFText.toHex(uValue) + "\n"
- + ">>\n"
- + "endobj\n";
+ private PDFEncryptionJCE(int objectNumber, PDFEncryptionParams params, PDFDocument pdf) {
+ setObjectNumber(objectNumber);
+ try {
+ digest = MessageDigest.getInstance("MD5");
+ } catch (NoSuchAlgorithmException e) {
+ throw new UnsupportedOperationException(e.getMessage());
+ }
+ setDocument(pdf);
+ EncryptionInitializer encryptionInitializer = new EncryptionInitializer(params);
+ encryptionInitializer.init();
}
/**
- * This method encrypts the passed data using the generated keys.
- * @param data The data to be encrypted
- * @param number The block number
- * @param generation The block generation
- * @return The encrypted data
+ * Creates and returns an encryption object.
+ *
+ * @param objectNumber the object number for the encryption dictionary
+ * @param params the encryption parameters
+ * @param pdf the PDF document to be encrypted
+ * @return the newly created encryption object
*/
- public byte[] encryptData(byte[] data, int number, int generation) {
- if (this.encryptionKey == null) {
- throw new IllegalStateException("PDF Encryption has not been initialized");
- }
- byte[] hash = calcHash(number, generation);
- return encryptWithHash(data, hash, hash.length);
+ public static PDFEncryption make(
+ int objectNumber, PDFEncryptionParams params, PDFDocument pdf) {
+ return new PDFEncryptionJCE(objectNumber, params, pdf);
}
/** {@inheritDoc} */
if (o == null) {
throw new IllegalStateException("No object number could be obtained for a PDF object");
}
- return encryptData(data, o.getObjectNumber(), o.getGeneration());
+ byte[] key = createEncryptionKey(o.getObjectNumber(), o.getGeneration());
+ return encryptWithKey(key, data);
}
- private byte[] calcHash(int number, int generation) {
- byte[] hash = new byte[this.encryptionKey.length + 5];
-
- int i = 0;
- while (i < this.encryptionKey.length) {
- hash[i] = this.encryptionKey[i]; i++;
- }
-
- hash[i++] = (byte) (number >>> 0);
- hash[i++] = (byte) (number >>> 8);
- hash[i++] = (byte) (number >>> 16);
- hash[i++] = (byte) (generation >>> 0);
- hash[i++] = (byte) (generation >>> 8);
- return hash;
- }
-
- /**
- * Creates PDFFilter for the encryption object
- * @param number The object number
- * @param generation The objects generation
- * @return The resulting filter
- */
- public PDFFilter makeFilter(int number, int generation) {
- return new EncryptionFilter(this, number, generation);
- }
-
- /**
- * Adds a PDFFilter to the PDFStream object
- * @param stream the stream to add an encryption filter to
- */
+ /** {@inheritDoc} */
public void applyFilter(AbstractPDFStream stream) {
stream.getFilterList().addFilter(
- this.makeFilter(stream.getObjectNumber(), stream.getGeneration()));
+ new EncryptionFilter(stream.getObjectNumber(), stream.getGeneration()));
}
/**
- * Represent the object in PDF
+ * Prepares the encryption dictionary for output to a PDF file.
*
- * @return the PDF
+ * @return the encryption dictionary as a byte array
*/
public byte[] toPDF() {
- if (this.dictionary == null) {
- throw new IllegalStateException("PDF Encryption has not been initialized");
+ assert encryptionDictionary != null;
+ return encode(this.encryptionDictionary);
+ }
+
+ /** {@inheritDoc} */
+ public String getTrailerEntry() {
+ PDFDocument doc = getDocumentSafely();
+ FileIDGenerator gen = doc.getFileIDGenerator();
+ return "/Encrypt " + getObjectNumber() + " "
+ + getGeneration() + " R\n"
+ + "/ID["
+ + PDFText.toHex(gen.getOriginalFileID())
+ + PDFText.toHex(gen.getUpdatedFileID())
+ + "]\n";
+ }
+
+ private static byte[] encryptWithKey(byte[] key, byte[] data) {
+ try {
+ final Cipher c = initCipher(key);
+ return c.doFinal(data);
+ } catch (IllegalBlockSizeException e) {
+ throw new IllegalStateException(e.getMessage());
+ } catch (BadPaddingException e) {
+ throw new IllegalStateException(e.getMessage());
}
+ }
- return encode(this.dictionary);
+ private static Cipher initCipher(byte[] key) {
+ try {
+ Cipher c = Cipher.getInstance("RC4");
+ SecretKeySpec keyspec = new SecretKeySpec(key, "RC4");
+ c.init(Cipher.ENCRYPT_MODE, keyspec);
+ return c;
+ } catch (InvalidKeyException e) {
+ throw new IllegalStateException(e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new UnsupportedOperationException(e);
+ } catch (NoSuchPaddingException e) {
+ throw new UnsupportedOperationException(e);
+ }
}
/**
- * {@inheritDoc}
+ * Applies Algorithm 3.1 from the PDF 1.4 Reference.
+ *
+ * @param objectNumber the object number
+ * @param generationNumber the generation number
+ * @return the key to use for encryption
*/
- public String getTrailerEntry() {
- return "/Encrypt " + getObjectNumber() + " "
- + getGeneration() + " R\n"
- + "/ID[" + getFileID(1) + getFileID(2) + "]\n";
+ private byte[] createEncryptionKey(int objectNumber, int generationNumber) {
+ // Step 1 passed in
+ // Step 2
+ byte[] md5Input = prepareMD5Input(objectNumber, generationNumber);
+ // Step 3
+ digest.reset();
+ byte[] hash = digest.digest(md5Input);
+ // Step 4
+ int keyLength = Math.min(16, md5Input.length);
+ byte[] key = new byte[keyLength];
+ System.arraycopy(hash, 0, key, 0, keyLength);
+ return key;
}
+
+ private byte[] prepareMD5Input(int objectNumber, int generationNumber) {
+ byte[] md5Input = new byte[encryptionKey.length + 5];
+ System.arraycopy(encryptionKey, 0, md5Input, 0, encryptionKey.length);
+ int i = encryptionKey.length;
+ md5Input[i++] = (byte) (objectNumber >>> 0);
+ md5Input[i++] = (byte) (objectNumber >>> 8);
+ md5Input[i++] = (byte) (objectNumber >>> 16);
+ md5Input[i++] = (byte) (generationNumber >>> 0);
+ md5Input[i++] = (byte) (generationNumber >>> 8);
+ return md5Input;
+ }
+
}
* Creates a new PDFEncryption instance if PDF encryption is available.
* @param objnum PDF object number
* @param params PDF encryption parameters
+ * @param pdf the PDF document to encrypt
* @return PDFEncryption the newly created instance, null if PDF encryption
* is unavailable.
*/
- public static PDFEncryption newInstance(int objnum, PDFEncryptionParams params) {
+ public static PDFEncryption newInstance(int objnum, PDFEncryptionParams params,
+ PDFDocument pdf) {
try {
- Class clazz = Class.forName("org.apache.fop.pdf.PDFEncryptionJCE");
+ Class<?> clazz = Class.forName("org.apache.fop.pdf.PDFEncryptionJCE");
Method makeMethod = clazz.getMethod("make",
- new Class[] {int.class, PDFEncryptionParams.class});
+ new Class[] {int.class, PDFEncryptionParams.class, PDFDocument.class});
Object obj = makeMethod.invoke(null,
- new Object[] {new Integer(objnum), params});
+ new Object[] {new Integer(objnum), params, pdf});
return (PDFEncryption)obj;
} catch (ClassNotFoundException e) {
if (checkAvailableAlgorithms()) {
private String userPassword = ""; //May not be null
private String ownerPassword = ""; //May not be null
+
private boolean allowPrint = true;
private boolean allowCopyContent = true;
private boolean allowEditContent = true;
private boolean allowEditAnnotations = true;
+ private boolean allowFillInForms = true;
+ private boolean allowAccessContent = true;
+ private boolean allowAssembleDocument = true;
+ private boolean allowPrintHq = true;
+
+ private int encryptionLengthInBits = 40;
/**
* Creates a new instance.
//nop
}
+ /**
+ * Creates a copy of the given encryption parameters.
+ *
+ * @param source source encryption parameters
+ */
+ public PDFEncryptionParams(PDFEncryptionParams source) {
+ setUserPassword(source.getUserPassword());
+ setOwnerPassword(source.getOwnerPassword());
+ setAllowPrint(source.isAllowPrint());
+ setAllowCopyContent(source.isAllowCopyContent());
+ setAllowEditContent(source.isAllowEditContent());
+ setAllowEditAnnotations(source.isAllowEditAnnotations());
+ setAllowAssembleDocument(source.isAllowAssembleDocument());
+ setAllowAccessContent(source.isAllowAccessContent());
+ setAllowFillInForms(source.isAllowFillInForms());
+ setAllowPrintHq(source.isAllowPrintHq());
+ setEncryptionLengthInBits(source.getEncryptionLengthInBits());
+ }
+
/**
* Indicates whether copying content is allowed.
* @return true if copying is allowed
return allowPrint;
}
+ /**
+ * Indicates whether revision 3 filling in forms is allowed.
+ * @return true if revision 3 filling in forms is allowed
+ */
+ public boolean isAllowFillInForms() {
+ return allowFillInForms;
+ }
+
+ /**
+ * Indicates whether revision 3 extracting text and graphics is allowed.
+ * @return true if revision 3 extracting text and graphics is allowed
+ */
+ public boolean isAllowAccessContent() {
+ return allowAccessContent;
+ }
+
+ /**
+ * Indicates whether revision 3 assembling document is allowed.
+ * @return true if revision 3 assembling document is allowed
+ */
+ public boolean isAllowAssembleDocument() {
+ return allowAssembleDocument;
+ }
+
+ /**
+ * Indicates whether revision 3 printing to high quality is allowed.
+ * @return true if revision 3 printing to high quality is allowed
+ */
+ public boolean isAllowPrintHq() {
+ return allowPrintHq;
+ }
+
/**
* Returns the owner password.
* @return the owner password, an empty string if no password applies
}
/**
- * Sets the persmission for printing.
+ * Sets the permission for printing.
* @param allowPrint true if printing is allowed
*/
public void setAllowPrint(boolean allowPrint) {
this.allowPrint = allowPrint;
}
+ /**
+ * Sets whether revision 3 filling in forms is allowed.
+ * @param allowFillInForms true if revision 3 filling in forms is allowed.
+ */
+ public void setAllowFillInForms(boolean allowFillInForms) {
+ this.allowFillInForms = allowFillInForms;
+ }
+
+ /**
+ * Sets whether revision 3 extracting text and graphics is allowed.
+ * @param allowAccessContent true if revision 3 extracting text and graphics is allowed
+ */
+ public void setAllowAccessContent(boolean allowAccessContent) {
+ this.allowAccessContent = allowAccessContent;
+ }
+
+ /**
+ * Sets whether revision 3 assembling document is allowed.
+ * @param allowAssembleDocument true if revision 3 assembling document is allowed
+ */
+ public void setAllowAssembleDocument(boolean allowAssembleDocument) {
+ this.allowAssembleDocument = allowAssembleDocument;
+ }
+
+ /**
+ * Sets whether revision 3 printing to high quality is allowed.
+ * @param allowPrintHq true if revision 3 printing to high quality is allowed
+ */
+ public void setAllowPrintHq(boolean allowPrintHq) {
+ this.allowPrintHq = allowPrintHq;
+ }
+
/**
* Sets the owner password.
* @param ownerPassword The owner password to set, null or an empty String
}
}
+ /**
+ * Returns the encryption length.
+ * @return the encryption length
+ */
+ public int getEncryptionLengthInBits() {
+ return encryptionLengthInBits;
+ }
+
+ /**
+ * Sets the encryption length.
+ *
+ * @param encryptionLength the encryption length
+ */
+ public void setEncryptionLengthInBits(int encryptionLength) {
+ this.encryptionLengthInBits = encryptionLength;
+ }
+
}
}
/**
- * Returns the object's generation.
+ * Returns this object's generation.
* @return the PDF Object generation
*/
public int getGeneration() {
String NO_EDIT_CONTENT = "noedit";
/** PDF encryption parameter: Forbids annotations, datatype: Boolean or "true"/"false" */
String NO_ANNOTATIONS = "noannotations";
+
+ /**
+ * PDF encryption parameter: Forbids filling in existing interactive forms, datatype:
+ * Boolean or "true"/"false"
+ */
+ String NO_FILLINFORMS = "nofillinforms";
+
+ /**
+ * PDF encryption parameter: Forbids extracting text and graphics, datatype: Boolean
+ * or "true"/"false"
+ */
+ String NO_ACCESSCONTENT = "noaccesscontent";
+
+ /**
+ * PDF encryption parameter: Forbids assembling document, datatype: Boolean or
+ * "true"/"false"
+ */
+ String NO_ASSEMBLEDOC = "noassembledoc";
+
+ /**
+ * PDF encryption parameter: Forbids printing to high quality, datatype: Boolean or
+ * "true"/"false"
+ */
+ String NO_PRINTHQ = "noprinthq";
+
+ /**
+ * PDF encryption length parameter: must be a multiple of 8 between 40 and 128,
+ * default value 40, datatype: int.
+ */
+ String ENCRYPTION_LENGTH = "encryption-length";
/** Rendering Options key for the PDF/A mode. */
String PDF_A_MODE = "pdf-a-mode";
/** Rendering Options key for the PDF/X mode. */
* @event.severity WARN
*/
void nonStandardStructureType(Object source, String fo, String type, String fallback);
+
+ /**
+ * The encryption length must be a multiple of 8 between 40 and 128.
+ *
+ * @param source the event source
+ * @param originalValue requested encryption length
+ * @param correctedValue corrected encryption length
+ * @event.severity WARN
+ */
+ void incorrectEncryptionLength(Object source, int originalValue, int correctedValue);
}
<catalogue xml:lang="en">
<message key="nonFullyResolvedLinkTargets">{count} link target{count,equals,1,,s} could not be fully resolved and now point{count,equals,1,,s} to the top of the page or {count,equals,1,is,are} dysfunctional.</message>
<message key="nonStandardStructureType">‘{type}’ is not a standard structure type defined by the PDF Reference. Falling back to ‘{fallback}’.</message>
+ <message key="incorrectEncryptionLength">Encryption length must be a multiple of 8 between 40 and 128. Setting encryption length to {correctedValue} instead of {originalValue}.</message>
</catalogue>
Configuration encryptionParamsConfig
= cfg.getChild(PDFConfigurationConstants.ENCRYPTION_PARAMS, false);
if (encryptionParamsConfig != null) {
- PDFEncryptionParams encryptionParams = new PDFEncryptionParams();
+ PDFEncryptionParams encryptionParams = pdfUtil.getEncryptionParams();
Configuration ownerPasswordConfig = encryptionParamsConfig.getChild(
PDFConfigurationConstants.OWNER_PASSWORD, false);
if (ownerPasswordConfig != null) {
if (noAnnotationsConfig != null) {
encryptionParams.setAllowEditAnnotations(false);
}
- pdfUtil.setEncryptionParams(encryptionParams);
+ Configuration noFillInForms = encryptionParamsConfig.getChild(
+ PDFConfigurationConstants.NO_FILLINFORMS, false);
+ if (noFillInForms != null) {
+ encryptionParams.setAllowFillInForms(false);
+ }
+ Configuration noAccessContentConfig = encryptionParamsConfig.getChild(
+ PDFConfigurationConstants.NO_ACCESSCONTENT, false);
+ if (noAccessContentConfig != null) {
+ encryptionParams.setAllowAccessContent(false);
+ }
+ Configuration noAssembleDocConfig = encryptionParamsConfig.getChild(
+ PDFConfigurationConstants.NO_ASSEMBLEDOC, false);
+ if (noAssembleDocConfig != null) {
+ encryptionParams.setAllowAssembleDocument(false);
+ }
+ Configuration noPrintHqConfig = encryptionParamsConfig.getChild(
+ PDFConfigurationConstants.NO_PRINTHQ, false);
+ if (noPrintHqConfig != null) {
+ encryptionParams.setAllowPrintHq(false);
+ }
+ Configuration encryptionLengthConfig = encryptionParamsConfig.getChild(
+ PDFConfigurationConstants.ENCRYPTION_LENGTH, false);
+ if (encryptionLengthConfig != null) {
+ int encryptionLength = checkEncryptionLength(
+ Integer.parseInt(encryptionLengthConfig.getValue(null)));
+ encryptionParams.setEncryptionLengthInBits(encryptionLength);
+ }
}
+
s = cfg.getChild(PDFConfigurationConstants.KEY_OUTPUT_PROFILE, true).getValue(null);
if (s != null) {
pdfUtil.setOutputProfileURI(s);
}
}
+ private int checkEncryptionLength(int encryptionLength) {
+ int correctEncryptionLength = encryptionLength;
+ if (encryptionLength < 40) {
+ correctEncryptionLength = 40;
+ } else if (encryptionLength > 128) {
+ correctEncryptionLength = 128;
+ } else if (encryptionLength % 8 != 0) {
+ correctEncryptionLength = ((int) Math.round(encryptionLength / 8.0f)) * 8;
+ }
+ if (correctEncryptionLength != encryptionLength) {
+ PDFEventProducer.Provider.get(userAgent.getEventBroadcaster())
+ .incorrectEncryptionLength(this, encryptionLength, correctEncryptionLength);
+ }
+ return correctEncryptionLength;
+ }
+
/**
* Builds a filter map from an Avalon Configuration object.
*
if (params != null) {
this.encryptionParams = params; //overwrite if available
}
- String pwd;
- pwd = (String)userAgent.getRendererOptions().get(USER_PASSWORD);
- if (pwd != null) {
- if (encryptionParams == null) {
- this.encryptionParams = new PDFEncryptionParams();
- }
- this.encryptionParams.setUserPassword(pwd);
+ String userPassword = (String)userAgent.getRendererOptions().get(USER_PASSWORD);
+ if (userPassword != null) {
+ getEncryptionParams().setUserPassword(userPassword);
}
- pwd = (String)userAgent.getRendererOptions().get(OWNER_PASSWORD);
- if (pwd != null) {
- if (encryptionParams == null) {
- this.encryptionParams = new PDFEncryptionParams();
- }
- this.encryptionParams.setOwnerPassword(pwd);
+ String ownerPassword = (String)userAgent.getRendererOptions().get(OWNER_PASSWORD);
+ if (ownerPassword != null) {
+ getEncryptionParams().setOwnerPassword(ownerPassword);
}
- Object setting;
- setting = userAgent.getRendererOptions().get(NO_PRINT);
- if (setting != null) {
- if (encryptionParams == null) {
- this.encryptionParams = new PDFEncryptionParams();
- }
- this.encryptionParams.setAllowPrint(!booleanValueOf(setting));
+ Object noPrint = userAgent.getRendererOptions().get(NO_PRINT);
+ if (noPrint != null) {
+ getEncryptionParams().setAllowPrint(!booleanValueOf(noPrint));
}
- setting = userAgent.getRendererOptions().get(NO_COPY_CONTENT);
- if (setting != null) {
- if (encryptionParams == null) {
- this.encryptionParams = new PDFEncryptionParams();
- }
- this.encryptionParams.setAllowCopyContent(!booleanValueOf(setting));
+ Object noCopyContent = userAgent.getRendererOptions().get(NO_COPY_CONTENT);
+ if (noCopyContent != null) {
+ getEncryptionParams().setAllowCopyContent(!booleanValueOf(noCopyContent));
}
- setting = userAgent.getRendererOptions().get(NO_EDIT_CONTENT);
- if (setting != null) {
- if (encryptionParams == null) {
- this.encryptionParams = new PDFEncryptionParams();
- }
- this.encryptionParams.setAllowEditContent(!booleanValueOf(setting));
+ Object noEditContent = userAgent.getRendererOptions().get(NO_EDIT_CONTENT);
+ if (noEditContent != null) {
+ getEncryptionParams().setAllowEditContent(!booleanValueOf(noEditContent));
}
- setting = userAgent.getRendererOptions().get(NO_ANNOTATIONS);
- if (setting != null) {
- if (encryptionParams == null) {
- this.encryptionParams = new PDFEncryptionParams();
- }
- this.encryptionParams.setAllowEditAnnotations(!booleanValueOf(setting));
+ Object noAnnotations = userAgent.getRendererOptions().get(NO_ANNOTATIONS);
+ if (noAnnotations != null) {
+ getEncryptionParams().setAllowEditAnnotations(!booleanValueOf(noAnnotations));
+ }
+ Object noFillInForms = userAgent.getRendererOptions().get(NO_FILLINFORMS);
+ if (noFillInForms != null) {
+ getEncryptionParams().setAllowFillInForms(!booleanValueOf(noFillInForms));
+ }
+ Object noAccessContent = userAgent.getRendererOptions().get(NO_ACCESSCONTENT);
+ if (noAccessContent != null) {
+ getEncryptionParams().setAllowAccessContent(!booleanValueOf(noAccessContent));
+ }
+ Object noAssembleDoc = userAgent.getRendererOptions().get(NO_ASSEMBLEDOC);
+ if (noAssembleDoc != null) {
+ getEncryptionParams().setAllowAssembleDocument(!booleanValueOf(noAssembleDoc));
+ }
+ Object noPrintHQ = userAgent.getRendererOptions().get(NO_PRINTHQ);
+ if (noPrintHQ != null) {
+ getEncryptionParams().setAllowPrintHq(!booleanValueOf(noPrintHQ));
}
String s = (String)userAgent.getRendererOptions().get(PDF_A_MODE);
if (s != null) {
if (s != null) {
this.outputProfileURI = s;
}
- setting = userAgent.getRendererOptions().get(KEY_DISABLE_SRGB_COLORSPACE);
- if (setting != null) {
- this.disableSRGBColorSpace = booleanValueOf(setting);
+ Object disableSRGBColorSpace = userAgent.getRendererOptions().get(
+ KEY_DISABLE_SRGB_COLORSPACE);
+ if (disableSRGBColorSpace != null) {
+ this.disableSRGBColorSpace = booleanValueOf(disableSRGBColorSpace);
}
}
}
/**
- * Sets the encryption parameters used by the PDF renderer.
- * @param encryptionParams the encryption parameters
+ * Gets the encryption parameters used by the PDF renderer.
+ * @return encryptionParams the encryption parameters
*/
- public void setEncryptionParams(PDFEncryptionParams encryptionParams) {
- this.encryptionParams = encryptionParams;
+ PDFEncryptionParams getEncryptionParams() {
+ if (this.encryptionParams == null) {
+ this.encryptionParams = new PDFEncryptionParams();
+ }
+ return this.encryptionParams;
}
private void updateInfo() {
documents. Example: the fix of marks layering will be such a case when it's done.
-->
<release version="FOP Trunk" date="TBD">
+ <action context="Renderers" dev="VH" type="add">
+ Added support for 128bit encryption in PDF output. Based on work by Michael Rubin.
+ </action>
<action context="Renderers" dev="PH" type="fix">
Fixed a bug in AFP where the object area axes of an Include Object was incorrectly set when
rotated by 180. </action>
import junit.framework.TestSuite;
import org.apache.fop.events.BasicEventTestCase;
+import org.apache.fop.pdf.FileIDGeneratorTestCase;
+import org.apache.fop.pdf.PDFEncryptionJCETestCase;
import org.apache.fop.pdf.PDFObjectTestCase;
import org.apache.fop.traits.BorderPropsTestCase;
import org.apache.fop.util.BitmapImageUtilTestCase;
//$JUnit-BEGIN$
suite.addTest(new TestSuite(PDFNumberTestCase.class));
suite.addTest(new TestSuite(PDFObjectTestCase.class));
+ suite.addTest(FileIDGeneratorTestCase.suite());
suite.addTest(new TestSuite(ColorUtilTestCase.class));
suite.addTest(new TestSuite(BorderPropsTestCase.class));
suite.addTest(new TestSuite(ElementListUtilsTestCase.class));
suite.addTest(new TestSuite(XMLResourceBundleTestCase.class));
suite.addTest(new TestSuite(URIResolutionTestCase.class));
suite.addTest(new TestSuite(BitmapImageUtilTestCase.class));
+ suite.addTest(new TestSuite(PDFEncryptionJCETestCase.class));
//$JUnit-END$
return suite;
}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf;
+
+import java.util.Arrays;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Tests the {@link FileIDGenerator} class.
+ */
+public abstract class FileIDGeneratorTestCase extends TestCase {
+
+ /**
+ * Returns a suite containing all the {@link FileIDGenerator} test cases.
+ *
+ * @return the test suite
+ */
+ public static final Test suite() {
+ TestSuite suite = new TestSuite(new Class[] {
+ RandomFileIDGeneratorTestCase.class,
+ DigestFileIDGeneratorTestCase.class },
+ FileIDGeneratorTestCase.class.getName());
+ return suite;
+ }
+
+ /** The generator under test. */
+ protected FileIDGenerator fileIDGenerator;
+
+
+ /** Tests that the getOriginalFileID method generates valid output. */
+ public void testOriginal() {
+ byte[] fileID = fileIDGenerator.getOriginalFileID();
+ fileIDMustBeValid(fileID);
+ }
+
+ /** Tests that the getUpdatedFileID method generates valid output. */
+ public void testUpdated() {
+ byte[] fileID = fileIDGenerator.getUpdatedFileID();
+ fileIDMustBeValid(fileID);
+ }
+
+ private void fileIDMustBeValid(byte[] fileID) {
+ assertNotNull(fileID);
+ assertEquals(16, fileID.length);
+ }
+
+ /** Tests that multiple calls to getOriginalFileID method always return the same value. */
+ public void testOriginalMultipleCalls() {
+ byte[] fileID1 = fileIDGenerator.getUpdatedFileID();
+ byte[] fileID2 = fileIDGenerator.getUpdatedFileID();
+ assertTrue(Arrays.equals(fileID1, fileID2));
+ }
+
+ /** Tests that getUpdatedFileID returns the same value as getOriginalFileID. */
+ public void testUpdateEqualsOriginal() {
+ byte[] originalFileID = fileIDGenerator.getOriginalFileID();
+ byte[] updatedFileID = fileIDGenerator.getUpdatedFileID();
+ assertTrue(Arrays.equals(originalFileID, updatedFileID));
+ }
+
+ /**
+ * Tests the random file ID generator.
+ */
+ public static class RandomFileIDGeneratorTestCase extends FileIDGeneratorTestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ fileIDGenerator = FileIDGenerator.getRandomFileIDGenerator();
+ }
+
+ }
+
+ /**
+ * Tests the file ID generator based on an MD5 digest.
+ */
+ public static class DigestFileIDGeneratorTestCase extends FileIDGeneratorTestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ fileIDGenerator = FileIDGenerator.getDigestFileIDGenerator(
+ new PDFDocument("Apache FOP"));
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the {@link PDFEncryptionJCE} class.
+ */
+public class PDFEncryptionJCETestCase extends TestCase {
+
+ private EncryptionTest test;
+
+ private PDFEncryptionJCE encryptionObject;
+
+ private static final class EncryptionTest {
+
+ private int objectNumber = 1;
+
+ private final PDFEncryptionParams encryptionParameters = new PDFEncryptionParams();
+
+ private byte[] data;
+
+ private byte[] encryptedData;
+
+ private final EncryptionDictionaryTester encryptionDictionaryTester;
+
+ EncryptionTest() {
+ this(new EncryptionDictionaryTester());
+ }
+
+ EncryptionTest(EncryptionDictionaryTester encryptionDictionaryTester) {
+ encryptionParameters.setUserPassword("TestUserPassword");
+ encryptionParameters.setOwnerPassword("TestOwnerPassword");
+ setData(0x00, 0xAA, 0xFF, 0x55, 0xCC, 0x33, 0xF0);
+ this.encryptionDictionaryTester = encryptionDictionaryTester;
+ this.encryptionDictionaryTester.setLength(
+ encryptionParameters.getEncryptionLengthInBits());
+ }
+
+ int getObjectNumber() {
+ return objectNumber;
+ }
+
+ EncryptionTest setObjectNumber(int objectNumber) {
+ this.objectNumber = objectNumber;
+ return this;
+ }
+
+ byte[] getData() {
+ return data;
+ }
+
+ EncryptionTest setData(int... data) {
+ /*
+ * Use an array of int to avoid having to cast some elements to byte in the
+ * method call.
+ */
+ this.data = convertIntArrayToByteArray(data);
+ return this;
+ }
+
+ byte[] getEncryptedData() {
+ return encryptedData;
+ }
+
+ EncryptionTest setEncryptedData(int... encryptedData) {
+ this.encryptedData = convertIntArrayToByteArray(encryptedData);
+ return this;
+ }
+
+ private byte[] convertIntArrayToByteArray(int[] intArray) {
+ byte[] byteArray = new byte[intArray.length];
+ for (int i = 0; i < intArray.length; i++) {
+ byteArray[i] = (byte) intArray[i];
+ }
+ return byteArray;
+ }
+
+ PDFEncryptionParams getEncryptionParameters() {
+ return encryptionParameters;
+ }
+
+ EncryptionTest setUserPassword(String userPassword) {
+ encryptionParameters.setUserPassword(userPassword);
+ return this;
+ }
+
+ EncryptionTest setOwnerPassword(String ownerPassword) {
+ encryptionParameters.setOwnerPassword(ownerPassword);
+ return this;
+ }
+
+ EncryptionTest setEncryptionLength(int encryptionLength) {
+ encryptionParameters.setEncryptionLengthInBits(encryptionLength);
+ encryptionDictionaryTester.setLength(encryptionLength);
+ return this;
+ }
+
+ EncryptionTest disablePrint() {
+ encryptionParameters.setAllowPrint(false);
+ return this;
+ }
+
+ EncryptionTest disableEditContent() {
+ encryptionParameters.setAllowEditContent(false);
+ return this;
+ }
+
+ EncryptionTest disableCopyContent() {
+ encryptionParameters.setAllowCopyContent(false);
+ return this;
+ }
+
+ EncryptionTest disableEditAnnotations() {
+ encryptionParameters.setAllowEditAnnotations(false);
+ return this;
+ }
+
+ EncryptionTest disableFillInForms() {
+ encryptionParameters.setAllowFillInForms(false);
+ return this;
+ }
+
+ EncryptionTest disableAccessContent() {
+ encryptionParameters.setAllowAccessContent(false);
+ return this;
+ }
+
+ EncryptionTest disableAssembleDocument() {
+ encryptionParameters.setAllowAssembleDocument(false);
+ return this;
+ }
+
+ EncryptionTest disablePrintHq() {
+ encryptionParameters.setAllowPrintHq(false);
+ return this;
+ }
+
+ void testEncryptionDictionary(PDFEncryptionJCE encryptionObject) {
+ encryptionDictionaryTester.test(encryptionObject);
+ }
+ }
+
+ private static final class EncryptionDictionaryTester {
+
+ private int version = 1;
+
+ private int revision = 2;
+
+ private int length = 40;
+
+ private int permissions = -4;
+
+ private String ownerEntry
+ = "3EE8C4000CA44B2645EED029C9EA7D4FC63C6D9B89349E8FA5A40C7691AB96B5";
+
+ private String userEntry
+ = "D1810D9E6E488BA5D2DDCBB3F974F7472D0D5389F554DB55574A787DC5C59884";
+
+ EncryptionDictionaryTester setVersion(int version) {
+ this.version = version;
+ return this;
+ }
+
+ EncryptionDictionaryTester setRevision(int revision) {
+ this.revision = revision;
+ return this;
+ }
+
+ EncryptionDictionaryTester setLength(int length) {
+ this.length = length;
+ return this;
+ }
+
+ EncryptionDictionaryTester setPermissions(int permissions) {
+ this.permissions = permissions;
+ return this;
+ }
+
+ EncryptionDictionaryTester setOwnerEntry(String ownerEntry) {
+ this.ownerEntry = ownerEntry;
+ return this;
+ }
+
+ EncryptionDictionaryTester setUserEntry(String userEntry) {
+ this.userEntry = userEntry;
+ return this;
+ }
+
+ void test(PDFEncryptionJCE encryptionObject) {
+ byte[] encryptionDictionary = encryptionObject.toPDF();
+ RegexTestedCharSequence dictionary = new RegexTestedCharSequence(encryptionDictionary);
+
+ final String whitespace = "\\s+";
+ final String digits = "\\d+";
+ final String hexDigits = "\\p{XDigit}+";
+
+ dictionary.mustContain("1" + whitespace + "0" + whitespace + "obj");
+
+ dictionary.mustContain("/Filter" + whitespace + "/Standard\\b");
+
+ dictionary.mustContain("/V" + whitespace + "(" + digits + ")")
+ .withGroup1EqualTo(Integer.toString(version));
+
+ dictionary.mustContain("/R" + whitespace + "(" + digits + ")")
+ .withGroup1EqualTo(Integer.toString(revision));
+
+ dictionary.mustContain("/Length" + whitespace + "(" + digits + ")")
+ .withGroup1EqualTo(Integer.toString(length));
+
+ dictionary.mustContain("/P" + whitespace + "(-?" + digits + ")")
+ .withGroup1EqualTo(Integer.toString(permissions));
+
+ dictionary.mustContain("/O" + whitespace + "<(" + hexDigits + ")>")
+ .withGroup1EqualTo(ownerEntry);
+
+ dictionary.mustContain("/U" + whitespace + "<(" + hexDigits + ")>")
+ .withGroup1EqualTo(userEntry);
+ }
+ }
+
+ private static final class RegexTestedCharSequence {
+
+ private final String string;
+
+ private Matcher matcher;
+
+ RegexTestedCharSequence(byte[] bytes) {
+ try {
+ string = new String(bytes, "US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ RegexTestedCharSequence mustContain(String regex) {
+ Pattern pattern = Pattern.compile(regex);
+ matcher = pattern.matcher(string);
+ assertTrue(matcher.find());
+ return this;
+ }
+
+ RegexTestedCharSequence withGroup1EqualTo(String expected) {
+ assertEquals(expected, matcher.group(1));
+ return this;
+ }
+ }
+
+ public final void testMake() {
+ PDFEncryption testEncryptionObj = createEncryptionObject(new PDFEncryptionParams());
+ assertTrue(testEncryptionObj instanceof PDFEncryptionJCE);
+ assertEquals(1, ((PDFEncryptionJCE) testEncryptionObj).getObjectNumber());
+ }
+
+ public void testBasic() throws IOException {
+ test = new EncryptionTest();
+ test.setData(0x00).setEncryptedData(0x56);
+ runEncryptionTests();
+
+ test.setData(0xAA).setEncryptedData(0xFC);
+ runEncryptionTests();
+
+ test.setData(0xFF).setEncryptedData(0xA9);
+ runEncryptionTests();
+
+ test = new EncryptionTest().setEncryptedData(0x56, 0x0C, 0xFC, 0xA5, 0xAB, 0x61, 0x73);
+ runEncryptionTests();
+ }
+
+ public void test128bit() throws IOException {
+ EncryptionDictionaryTester encryptionDictionaryTester = new EncryptionDictionaryTester()
+ .setVersion(2)
+ .setRevision(3)
+ .setPermissions(-4)
+ .setOwnerEntry("D9A98017F0500EF9B69738641C9B4CBA1229EDC3F2151BC6C9C4FB07B1CB315E")
+ .setUserEntry("D3EF424BFEA2E434000E1A74941CC87300000000000000000000000000000000");
+ test = new EncryptionTest(encryptionDictionaryTester)
+ .setObjectNumber(2)
+ .setEncryptionLength(128)
+ .setEncryptedData(0xE3, 0xCB, 0xB2, 0x55, 0xD9, 0x26, 0x55);
+ runEncryptionTests();
+ }
+
+ public void testDisableRev2Permissions() throws IOException {
+ EncryptionDictionaryTester encryptionDictionaryTester = new EncryptionDictionaryTester()
+ .setPermissions(-64)
+ .setUserEntry("3E65D0090746C4C37C5EF23C1BDB6323E00C24C4B2D744DD3BFB654CD58591A1");
+ test = new EncryptionTest(encryptionDictionaryTester)
+ .setObjectNumber(3)
+ .disablePrint()
+ .disableEditContent()
+ .disableCopyContent()
+ .disableEditAnnotations()
+ .setEncryptedData(0x66, 0xEE, 0xA7, 0x93, 0xC4, 0xB1, 0xB4);
+ runEncryptionTests();
+ }
+
+ public void testDisableRev3Permissions() throws IOException {
+ EncryptionDictionaryTester encryptionDictionaryTester = new EncryptionDictionaryTester()
+ .setVersion(2)
+ .setRevision(3)
+ .setPermissions(-3844)
+ .setOwnerEntry("8D4BCA4F4AB2BAB4E38F161D61F937EC50BE5EB30C2DC05EA409D252CD695E55")
+ .setUserEntry("0F01171E22C7FB27B079C132BA4277DE00000000000000000000000000000000");
+ test = new EncryptionTest(encryptionDictionaryTester)
+ .setObjectNumber(4)
+ .disableFillInForms()
+ .disableAccessContent()
+ .disableAssembleDocument()
+ .disablePrintHq()
+ .setEncryptedData(0x8E, 0x3C, 0xD2, 0x05, 0x50, 0x48, 0x82);
+ runEncryptionTests();
+ }
+
+ public void test128bitDisableSomePermissions() throws IOException {
+ EncryptionDictionaryTester encryptionDictionaryTester = new EncryptionDictionaryTester()
+ .setVersion(2)
+ .setRevision(3)
+ .setPermissions(-1304)
+ .setOwnerEntry("D9A98017F0500EF9B69738641C9B4CBA1229EDC3F2151BC6C9C4FB07B1CB315E")
+ .setUserEntry("62F0E4D8641D482E0F8E71A89270045A00000000000000000000000000000000");
+ test = new EncryptionTest(encryptionDictionaryTester)
+ .setObjectNumber(5)
+ .setEncryptionLength(128)
+ .disablePrint()
+ .disableCopyContent()
+ .disableFillInForms()
+ .disableAssembleDocument()
+ .setEncryptedData(0xF7, 0x85, 0x4F, 0xB0, 0x50, 0x5C, 0xDF);
+ runEncryptionTests();
+ }
+
+ public void testDifferentPasswords() throws IOException {
+ EncryptionDictionaryTester encryptionDictionaryTester = new EncryptionDictionaryTester()
+ .setOwnerEntry("D11C233C65E9DC872E858ABBD8B62198771167ADCE7AB8DC7AE0A1A7E21A1E25")
+ .setUserEntry("6F449167DB8DDF0D2DF4602DDBBA97ABF9A9101F632CC16AB0BE74EB9500B469");
+ test = new EncryptionTest(encryptionDictionaryTester)
+ .setObjectNumber(6)
+ .setUserPassword("ADifferentUserPassword")
+ .setOwnerPassword("ADifferentOwnerPassword")
+ .setEncryptedData(0x27, 0xAC, 0xB1, 0x6C, 0x42, 0xE0, 0xA8);
+ runEncryptionTests();
+ }
+
+ public void testNoOwnerPassword() throws IOException {
+ EncryptionDictionaryTester encryptionDictionaryTester = new EncryptionDictionaryTester()
+ .setOwnerEntry("5163AAF3EE74C76D7C223593A84C8702FEA8AA4493E4933FF5B5A5BBB20AE4BB")
+ .setUserEntry("42DDF1C1BF3AB04786D5038E7B0A723AE614D944E1DE91A922FC54F5F2345E00");
+ test = new EncryptionTest(encryptionDictionaryTester)
+ .setObjectNumber(7)
+ .setUserPassword("ADifferentUserPassword")
+ .setOwnerPassword("")
+ .setEncryptedData(0xEC, 0x2E, 0x5D, 0xC2, 0x7F, 0xAD, 0x58);
+ runEncryptionTests();
+ }
+
+ public void test128bitDisableSomePermissionsDifferentPasswords() throws IOException {
+ EncryptionDictionaryTester encryptionDictionaryTester = new EncryptionDictionaryTester()
+ .setVersion(2)
+ .setRevision(3)
+ .setPermissions(-2604)
+ .setOwnerEntry("F83CA049FAA2F774F8541F25E746A92EE2A7F060C46C91C693E673BF18FF7B36")
+ .setUserEntry("88A4C58F5385B5F08FACA0636D790EDF00000000000000000000000000000000");
+ test = new EncryptionTest(encryptionDictionaryTester)
+ .setObjectNumber(8)
+ .setUserPassword("ADifferentUserPassword")
+ .setOwnerPassword("ADifferentOwnerPassword")
+ .setEncryptionLength(128)
+ .disableEditContent()
+ .disableEditAnnotations()
+ .disableAccessContent()
+ .disablePrintHq()
+ .setEncryptedData(0x77, 0x54, 0x67, 0xA5, 0xCC, 0x73, 0xDE);
+ runEncryptionTests();
+ }
+
+ public void test128bitNoPermissionNoOwnerPassword() throws IOException {
+ EncryptionDictionaryTester encryptionDictionaryTester = new EncryptionDictionaryTester()
+ .setVersion(2)
+ .setRevision(3)
+ .setPermissions(-3904)
+ .setOwnerEntry("3EEB3FA5594CBD935BFB2F83FB184DD41FBCD7C36A04F1FFD0899B0DFFCFF96B")
+ .setUserEntry("D972B72DD2633F613B0DDB7511C719C500000000000000000000000000000000");
+ test = new EncryptionTest(encryptionDictionaryTester)
+ .setObjectNumber(9)
+ .setUserPassword("ADifferentUserPassword")
+ .setOwnerPassword("")
+ .setEncryptionLength(128)
+ .disablePrint()
+ .disableEditContent()
+ .disableCopyContent()
+ .disableEditAnnotations()
+ .disableFillInForms()
+ .disableAccessContent()
+ .disableAssembleDocument()
+ .disablePrintHq()
+ .setEncryptedData(0x0C, 0xAD, 0x49, 0xC7, 0xE5, 0x05, 0xB8);
+ runEncryptionTests();
+ }
+
+ /**
+ * Creates an encryption object using a fixed file ID generator for test reproducibility.
+ *
+ * @param params the encryption parameters
+ * @return PDFEncryptionJCE the encryption object
+ */
+ private PDFEncryptionJCE createEncryptionObject(PDFEncryptionParams params) {
+ PDFDocument doc = new PDFDocument("Apache FOP") {
+
+ @Override
+ FileIDGenerator getFileIDGenerator() {
+ return new FileIDGenerator() {
+
+ private final byte[] fixedFileID = new byte[] {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F};
+
+ @Override
+ byte[] getOriginalFileID() {
+ return fixedFileID;
+ }
+
+ @Override
+ byte[] getUpdatedFileID() {
+ return fixedFileID;
+ }
+
+ };
+ }
+ };
+ return (PDFEncryptionJCE) PDFEncryptionJCE.make(1, params, doc);
+ }
+
+ private void runEncryptionTests() throws IOException {
+ encryptionObject = createEncryptionObject(test.getEncryptionParameters());
+ runEncryptTest();
+ runFilterTest();
+ runEncryptionDictionaryTest();
+ }
+
+ private void runEncryptTest() {
+ PDFText text = new PDFText();
+ text.setObjectNumber(test.getObjectNumber());
+ byte[] byteResult = encryptionObject.encrypt(test.getData(), text);
+
+ assertTrue(Arrays.equals(test.getEncryptedData(), byteResult));
+ }
+
+ private void runFilterTest() throws IOException {
+ PDFStream stream = new PDFStream();
+ stream.setDocument(encryptionObject.getDocumentSafely());
+ stream.setObjectNumber(test.getObjectNumber());
+ stream.setData(test.getData());
+ encryptionObject.applyFilter(stream);
+
+ StreamCache streamCache = stream.encodeStream();
+ ByteArrayOutputStream testOutputStream = new ByteArrayOutputStream();
+ streamCache.outputContents(testOutputStream);
+
+ assertTrue(Arrays.equals(test.getEncryptedData(), testOutputStream.toByteArray()));
+ }
+
+ private void runEncryptionDictionaryTest() {
+ test.testEncryptionDictionary(encryptionObject);
+ }
+
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.pdf;
+
+import java.io.File;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+import org.apache.fop.apps.FOPException;
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.apps.FopFactory;
+import org.apache.fop.events.Event;
+import org.apache.fop.events.EventListener;
+import org.apache.fop.pdf.PDFEncryptionParams;
+
+/**
+ * Tests that encryption length is properly set up.
+ */
+public class PDFRendererConfiguratorTestCase extends TestCase {
+
+ private FOUserAgent foUserAgent;
+
+ private PDFDocumentHandler documentHandler;
+
+ private boolean eventTriggered;
+
+ private class EncryptionEventFilter implements EventListener {
+
+ private final int specifiedEncryptionLength;
+
+ private final int correctedEncryptionLength;
+
+ EncryptionEventFilter(int specifiedEncryptionLength, int correctedEncryptionLength) {
+ this.specifiedEncryptionLength = specifiedEncryptionLength;
+ this.correctedEncryptionLength = correctedEncryptionLength;
+ }
+
+ public void processEvent(Event event) {
+ assertEquals(PDFEventProducer.class.getName() + ".incorrectEncryptionLength",
+ event.getEventID());
+ assertEquals(specifiedEncryptionLength, event.getParam("originalValue"));
+ assertEquals(correctedEncryptionLength, event.getParam("correctedValue"));
+ eventTriggered = true;
+ }
+ }
+
+ /**
+ * Non-multiple of 8 should be rounded.
+ *
+ * @throws Exception if an error occurs
+ */
+ public void testRoundUp() throws Exception {
+ runTest("roundUp", 55, 56);
+ }
+
+ /**
+ * Non-multiple of 8 should be rounded.
+ *
+ * @throws Exception if an error occurs
+ */
+ public void testRoundDown() throws Exception {
+ runTest("roundDown", 67, 64);
+ }
+
+ /**
+ * Encryption length must be at least 40.
+ *
+ * @throws Exception if an error occurs
+ */
+ public void testBelow40() throws Exception {
+ runTest("below40", 32, 40);
+ }
+
+ /**
+ * Encryption length must be at most 128.
+ *
+ * @throws Exception if an error occurs
+ */
+ public void testAbove128() throws Exception {
+ runTest("above128", 233, 128);
+ }
+
+ /**
+ * A correct value must be properly set up.
+ *
+ * @throws Exception if an error occurs
+ */
+ public void testCorrectValue() throws Exception {
+ givenAConfigurationFile("correct", new EventListener() {
+
+ public void processEvent(Event event) {
+ throw new AssertionFailedError("No event was expected");
+ }
+ });
+ whenCreatingAndConfiguringDocumentHandler();
+ thenEncryptionLengthShouldBe(128);
+
+ }
+
+ private void runTest(String configFilename,
+ final int specifiedEncryptionLength,
+ final int correctedEncryptionLength) throws Exception {
+ givenAConfigurationFile(configFilename,
+ new EncryptionEventFilter(specifiedEncryptionLength, correctedEncryptionLength));
+ whenCreatingAndConfiguringDocumentHandler();
+ assertTrue(eventTriggered);
+ }
+
+ private void givenAConfigurationFile(String filename, EventListener eventListener)
+ throws Exception {
+ FopFactory fopFactory = FopFactory.newInstance();
+ fopFactory.setUserConfig(new File("test/resources/org/apache/fop/render/pdf/"
+ + filename + ".xconf"));
+ foUserAgent = fopFactory.newFOUserAgent();
+ foUserAgent.getEventBroadcaster().addEventListener(eventListener);
+ }
+
+ private void whenCreatingAndConfiguringDocumentHandler() throws FOPException {
+ PDFDocumentHandlerMaker maker = new PDFDocumentHandlerMaker();
+ documentHandler = (PDFDocumentHandler) maker.makeIFDocumentHandler(foUserAgent);
+ new PDFRendererConfigurator(foUserAgent).configure(documentHandler);
+ }
+
+ private void thenEncryptionLengthShouldBe(int expectedEncryptionLength) {
+ PDFEncryptionParams encryptionParams = documentHandler.getPDFUtil().getEncryptionParams();
+ assertEquals(expectedEncryptionLength, encryptionParams.getEncryptionLengthInBits());
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.pdf;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+
+/**
+ * A test suite for org.apache.fop.render.pdf.*
+ */
+public final class RenderPDFTestSuite {
+
+ private RenderPDFTestSuite() { }
+
+ /**
+ * Creates the test suite.
+ *
+ * @return the test suite
+ */
+ public static Test suite() {
+ TestSuite suite = new TestSuite();
+ //$JUnit-BEGIN$
+ suite.addTest(new TestSuite(PDFRendererConfiguratorTestCase.class));
+ //$JUnit-END$
+ return suite;
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<fop version="1.0">
+ <renderers>
+ <renderer mime="application/pdf">
+ <encryption-params>
+ <encryption-length>233</encryption-length>
+ </encryption-params>
+ </renderer>
+ </renderers>
+</fop>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<fop version="1.0">
+ <renderers>
+ <renderer mime="application/pdf">
+ <encryption-params>
+ <encryption-length>32</encryption-length>
+ </encryption-params>
+ </renderer>
+ </renderers>
+</fop>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<fop version="1.0">
+ <renderers>
+ <renderer mime="application/pdf">
+ <encryption-params>
+ <encryption-length>128</encryption-length>
+ </encryption-params>
+ </renderer>
+ </renderers>
+</fop>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<fop version="1.0">
+ <renderers>
+ <renderer mime="application/pdf">
+ <encryption-params>
+ <encryption-length>67</encryption-length>
+ </encryption-params>
+ </renderer>
+ </renderers>
+</fop>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<fop version="1.0">
+ <renderers>
+ <renderer mime="application/pdf">
+ <encryption-params>
+ <encryption-length>55</encryption-length>
+ </encryption-params>
+ </renderer>
+ </renderers>
+</fop>