]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Added support for 128bit encryption in PDF output. Based on work by Michael Rubin.
authorVincent Hennebert <vhennebert@apache.org>
Mon, 8 Aug 2011 15:51:43 +0000 (15:51 +0000)
committerVincent Hennebert <vhennebert@apache.org>
Mon, 8 Aug 2011 15:51:43 +0000 (15:51 +0000)
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1154998 13f79535-47bb-0310-9956-ffa450edef68

28 files changed:
build.xml
src/documentation/content/xdocs/trunk/configuration.xml
src/documentation/content/xdocs/trunk/pdfencryption.xml
src/documentation/content/xdocs/trunk/running.xml
src/java/org/apache/fop/cli/CommandLineOptions.java
src/java/org/apache/fop/pdf/FileIDGenerator.java [new file with mode: 0644]
src/java/org/apache/fop/pdf/PDFDocument.java
src/java/org/apache/fop/pdf/PDFEncryption.java
src/java/org/apache/fop/pdf/PDFEncryptionJCE.java
src/java/org/apache/fop/pdf/PDFEncryptionManager.java
src/java/org/apache/fop/pdf/PDFEncryptionParams.java
src/java/org/apache/fop/pdf/PDFObject.java
src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java
src/java/org/apache/fop/render/pdf/PDFEventProducer.java
src/java/org/apache/fop/render/pdf/PDFEventProducer.xml
src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java
src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java
status.xml
test/java/org/apache/fop/UtilityCodeTestSuite.java
test/java/org/apache/fop/pdf/FileIDGeneratorTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/pdf/PDFEncryptionJCETestCase.java [new file with mode: 0644]
test/java/org/apache/fop/render/pdf/PDFRendererConfiguratorTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/render/pdf/RenderPDFTestSuite.java [new file with mode: 0644]
test/resources/org/apache/fop/render/pdf/above128.xconf [new file with mode: 0644]
test/resources/org/apache/fop/render/pdf/below40.xconf [new file with mode: 0644]
test/resources/org/apache/fop/render/pdf/correct.xconf [new file with mode: 0644]
test/resources/org/apache/fop/render/pdf/roundDown.xconf [new file with mode: 0644]
test/resources/org/apache/fop/render/pdf/roundUp.xconf [new file with mode: 0644]

index cfad23a747a03bcbb56dbd45f2c3a20a307f0352..1dd2ca689a9c647fc49f9ee22eb842ccca7e9a0b 100644 (file)
--- a/build.xml
+++ b/build.xml
@@ -962,7 +962,13 @@ list of possible build targets.
       <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>
index 26707edbf739d9d156d1d1592701c34d76e23069..e5469d037f6526fa791d0e255aa5d8a968aaf9f4 100644 (file)
       <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>
       
index 22d96505777b1a920e847a3dd9d1812b2ae7a8bd..3562bb5912f7fa6c464a62a107d23ff17ea0a56a 100644 (file)
         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.
@@ -151,6 +216,10 @@ Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, userAgent);
         <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 
@@ -163,6 +232,10 @@ Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, userAgent);
         <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>
index 11dc2848a85c935ac3ae2398b41ae450f0ffff8e..49ba7efd8e9591100e8ba5723caf7ccbbc0890dd 100644 (file)
@@ -128,6 +128,10 @@ Fop [options] [-fo|-xml] infile [-xsl file] [-awt|-pdf|-mif|-rtf|-tiff|-png|-pcl
   -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)
index 2a5c0d272f2f1713306287fe34353a2f572a570b..0d4c3790c764a01bfa8290f39c56f60eef28ef22 100644 (file)
@@ -381,6 +381,14 @@ public class CommandLineOptions {
                 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;
@@ -1181,6 +1189,14 @@ public class CommandLineOptions {
             + "  -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"
diff --git a/src/java/org/apache/fop/pdf/FileIDGenerator.java b/src/java/org/apache/fop/pdf/FileIDGenerator.java
new file mode 100644 (file)
index 0000000..00aad44
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * 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);
+    }
+}
index 9268ae921b678ccfc02da1c5b1baa4d44888953c..cbca3ea8f9c0f5f315e8b0ae7db95bf67352806d 100644 (file)
@@ -25,10 +25,7 @@ import java.io.InputStream;
 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;
@@ -186,6 +183,8 @@ public class PDFDocument {
 
     private boolean encodingOnTheFly = true;
 
+    private FileIDGenerator fileIDGenerator;
+
     /**
      * Creates an empty PDF document.
      *
@@ -513,10 +512,10 @@ public class PDFDocument {
      */
     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(
@@ -979,27 +978,6 @@ public class PDFDocument {
         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
      *
@@ -1038,7 +1016,9 @@ public class PDFDocument {
         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")
@@ -1089,4 +1069,18 @@ public class PDFDocument {
         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;
+    }
 }
index 5852df1570e83b73db2e2df713ceeea9c9a9ff95..277cf0a94e79e24e38382e7cd299bfc4b8f90b64 100644 (file)
@@ -24,18 +24,6 @@ package org.apache.fop.pdf;
  */
 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
index 269f0639d7516b210e349f37907c1f59f017f26b..c9b9c58baf5c0faab6d5ab0f32f1224f62bc88fa 100644 (file)
 
 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;
@@ -35,310 +34,427 @@ import javax.crypto.NoSuchPaddingException;
 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} */
@@ -350,63 +466,95 @@ public class PDFEncryptionJCE extends PDFObject implements PDFEncryption {
         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;
+    }
+
 }
index 3f5ae2a3deb52431c837922d9ab86febdf801798..6e57b1518999765a4fc55f726f110055e83140db 100644 (file)
@@ -109,16 +109,18 @@ public final class PDFEncryptionManager {
      * 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()) {
index 9cc502c42275d519a54fd06625504ad46b95e45b..71dccd867b0a4743421c0abf43c4dfae9bbed70f 100644 (file)
@@ -26,10 +26,17 @@ public class PDFEncryptionParams {
 
     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.
@@ -60,6 +67,25 @@ public class PDFEncryptionParams {
         //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
@@ -92,6 +118,38 @@ public class PDFEncryptionParams {
         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
@@ -133,13 +191,45 @@ public class PDFEncryptionParams {
     }
 
     /**
-     * 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
@@ -166,4 +256,21 @@ public class PDFEncryptionParams {
         }
     }
 
+    /**
+     * 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;
+    }
+
 }
index 09b8673a828d9a66586b6930fd294f2cc4026d0e..6dd0c800fbff346cf88fb517975110639a95361c 100644 (file)
@@ -112,7 +112,7 @@ public abstract class PDFObject implements PDFWritable {
     }
 
     /**
-     * Returns the object's generation.
+     * Returns this object's generation.
      * @return the PDF Object generation
      */
     public int getGeneration() {
index 841dd7e01afefcb43b711ea4dfe9298dcbeea578..994cbc0de1cac2152fa8703f59cab2675adc46a5 100644 (file)
@@ -38,6 +38,36 @@ public interface PDFConfigurationConstants {
     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. */
index 1e1ddf98a43ef32e9163b6e785c71fc547b13d5e..40062f73fd083fbf649633fe4fe45ed477dd1148 100644 (file)
@@ -65,4 +65,14 @@ public interface PDFEventProducer extends EventProducer {
      * @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);
 }
index 7f3c9d60979c494b350ad06a7ee9792992af0f24..bf930ea344d551a405e2ad4e6f0427e3ed902a4f 100644 (file)
@@ -2,4 +2,5 @@
 <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>
index 9ebb1d5a180e68ddeaad97288c7ef037e50d96bb..dcc7dd32e3bf170c16dd596be81d3c4b36757f10 100644 (file)
@@ -81,7 +81,7 @@ public class PDFRendererConfigurator extends PrintRendererConfigurator {
             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) {
@@ -118,8 +118,35 @@ public class PDFRendererConfigurator extends PrintRendererConfigurator {
             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);
@@ -132,6 +159,22 @@ public class PDFRendererConfigurator extends PrintRendererConfigurator {
         }
     }
 
+    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.
      *
index e630594725fd1579b677f33866a349972a7abadf..c662b0345cfb0c77c80b328796c0ffdee68f18e9 100644 (file)
@@ -124,49 +124,45 @@ class PDFRenderingUtil implements PDFConfigurationConstants {
         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) {
@@ -184,9 +180,10 @@ class PDFRenderingUtil implements PDFConfigurationConstants {
         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);
         }
     }
 
@@ -236,11 +233,14 @@ class PDFRenderingUtil implements PDFConfigurationConstants {
     }
 
     /**
-     * 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() {
index 25f8b5e2a1f6dbc7dad7188228aeb838bec0db3c..37231bfc25d6dd1775fe74e402dac3f80c65f269 100644 (file)
@@ -60,6 +60,9 @@
       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>
index 004b8f3c3bf0ddc19d44ebb61b5e19bfa7e47e96..9ef3c55103699593cb4d8583d27bdb5301b61ed2 100644 (file)
@@ -23,6 +23,8 @@ import junit.framework.Test;
 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;
@@ -46,6 +48,7 @@ public class UtilityCodeTestSuite {
         //$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));
@@ -53,6 +56,7 @@ public class UtilityCodeTestSuite {
         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;
     }
diff --git a/test/java/org/apache/fop/pdf/FileIDGeneratorTestCase.java b/test/java/org/apache/fop/pdf/FileIDGeneratorTestCase.java
new file mode 100644 (file)
index 0000000..3e96177
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * 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"));
+        }
+
+    }
+
+}
diff --git a/test/java/org/apache/fop/pdf/PDFEncryptionJCETestCase.java b/test/java/org/apache/fop/pdf/PDFEncryptionJCETestCase.java
new file mode 100644 (file)
index 0000000..c850181
--- /dev/null
@@ -0,0 +1,492 @@
+/*
+ * 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);
+    }
+
+}
diff --git a/test/java/org/apache/fop/render/pdf/PDFRendererConfiguratorTestCase.java b/test/java/org/apache/fop/render/pdf/PDFRendererConfiguratorTestCase.java
new file mode 100644 (file)
index 0000000..01889e4
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * 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());
+    }
+}
diff --git a/test/java/org/apache/fop/render/pdf/RenderPDFTestSuite.java b/test/java/org/apache/fop/render/pdf/RenderPDFTestSuite.java
new file mode 100644 (file)
index 0000000..c9a17da
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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;
+    }
+}
diff --git a/test/resources/org/apache/fop/render/pdf/above128.xconf b/test/resources/org/apache/fop/render/pdf/above128.xconf
new file mode 100644 (file)
index 0000000..2bdab04
--- /dev/null
@@ -0,0 +1,10 @@
+<?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>
diff --git a/test/resources/org/apache/fop/render/pdf/below40.xconf b/test/resources/org/apache/fop/render/pdf/below40.xconf
new file mode 100644 (file)
index 0000000..19086f7
--- /dev/null
@@ -0,0 +1,10 @@
+<?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>
diff --git a/test/resources/org/apache/fop/render/pdf/correct.xconf b/test/resources/org/apache/fop/render/pdf/correct.xconf
new file mode 100644 (file)
index 0000000..246c17e
--- /dev/null
@@ -0,0 +1,10 @@
+<?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>
diff --git a/test/resources/org/apache/fop/render/pdf/roundDown.xconf b/test/resources/org/apache/fop/render/pdf/roundDown.xconf
new file mode 100644 (file)
index 0000000..722808c
--- /dev/null
@@ -0,0 +1,10 @@
+<?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>
diff --git a/test/resources/org/apache/fop/render/pdf/roundUp.xconf b/test/resources/org/apache/fop/render/pdf/roundUp.xconf
new file mode 100644 (file)
index 0000000..ffe0690
--- /dev/null
@@ -0,0 +1,10 @@
+<?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>