]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Added support for PDF encryption.
authorJeremias Maerki <jeremias@apache.org>
Thu, 13 Mar 2003 16:46:05 +0000 (16:46 +0000)
committerJeremias Maerki <jeremias@apache.org>
Thu, 13 Mar 2003 16:46:05 +0000 (16:46 +0000)
Submitted by: Patrick C. Lankswert <PLankswert@InsightBB.COM>

Enhanced to be disabled automatically if JCE and/or necessary algorithms are unavailable.
PDF encryption doesn't work, yet. If it's enabled Acrobat will show blank pages. Don't know why, yet. See separate mail.

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@196089 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/fop/fo/FOUserAgent.java
src/java/org/apache/fop/pdf/PDFDocument.java
src/java/org/apache/fop/pdf/PDFEncryption.java [new file with mode: 0644]
src/java/org/apache/fop/pdf/PDFEncryptionJCE.java [new file with mode: 0644]
src/java/org/apache/fop/pdf/PDFEncryptionManager.java [new file with mode: 0644]
src/java/org/apache/fop/pdf/PDFEncryptionParams.java [new file with mode: 0644]
src/java/org/apache/fop/render/pdf/PDFRenderer.java

index b35fd642668ecc227490d765cef31589fc8c8503..713e4c535501bbc1ff48ae659a1f350006e27aa6 100644 (file)
@@ -63,6 +63,7 @@ import org.apache.avalon.framework.logger.LogEnabled;
 import org.apache.avalon.framework.logger.Logger;
 
 // FOP
+import org.apache.fop.pdf.PDFEncryptionParams;
 import org.apache.fop.render.XMLHandler;
 import org.apache.fop.render.RendererContext;
 
@@ -92,6 +93,7 @@ public class FOUserAgent implements LogEnabled {
     private Map defaults = new java.util.HashMap();
     private Map handlers = new java.util.HashMap();
     private String baseURL;
+    private PDFEncryptionParams pdfEncryptionParams;
 
     /**
      * Sets the logger.
@@ -131,6 +133,24 @@ public class FOUserAgent implements LogEnabled {
         }
     }
 
+    /**
+     * Returns the parameters for PDF encryption.
+     * @return the PDF encryption parameters, null if not applicable
+     */
+    public PDFEncryptionParams getPDFEncryptionParams() {
+        return pdfEncryptionParams;
+    }
+
+    /**
+     * Sets the parameters for PDF encryption.
+     * @param pdfEncryptionParams the PDF encryption parameters, null to 
+     * disable PDF encryption
+     */
+    public void setPDFEncryptionParams(PDFEncryptionParams pdfEncryptionParams) {
+        this.pdfEncryptionParams = pdfEncryptionParams;
+    }
+    
+    
     /**
      * Get an input stream for a reference.
      * Temporary solution until API better.
@@ -216,5 +236,6 @@ public class FOUserAgent implements LogEnabled {
                     + "No handler defined for XML: " + namespace);
         }
     }
+
 }
 
index 5e9c24d793d625c8de34e0f5081455c5c1ec8727..26b356c239b0e2878f4269515c6494864e4b8edd 100644 (file)
@@ -158,6 +158,11 @@ public class PDFDocument {
      */
     protected PDFResources resources;
 
+    /**
+     * the documents encryption, if exists
+     */
+    protected PDFEncryption encryption;
+
     /**
      * the colorspace (0=RGB, 1=CMYK)
      */
@@ -298,6 +303,30 @@ public class PDFDocument {
         return filterMap;
     }
 
+    /**
+     * Enables PDF encryption.
+     * @param params The encryption parameters for the pdf file
+     */
+    public void setEncryption(PDFEncryptionParams params) {
+        this.encryption = PDFEncryptionManager.newInstance(++this.objectcount, params);
+        if (encryption != null) {
+            /**@todo this cast is ugly. PDFObject should be transformed to an interface. */
+            addTrailerObject((PDFObject)this.encryption);
+        } else {
+            System.out.println("PDF encryption is unavailable. PDF will be "
+                + "generated without encryption.");
+        }
+    }
+    
+    
+    /**
+     * Indicates whether encryption is active for this PDF or not.
+     * @return boolean True if encryption is active
+     */
+    public boolean isEncryptionActive() {
+        return this.encryption != null;
+    }
+    
     /**
      * Make a /Catalog (Root) object. This object is written in
      * the trailer.
@@ -1696,7 +1725,7 @@ public class PDFDocument {
     }
 
     /**
-     * make a stream object
+     * Make a stream object
      *
      * @param type the type of stream to be created
      * @param add if true then the stream will be added immediately
@@ -1704,13 +1733,13 @@ public class PDFDocument {
      */
     public PDFStream makeStream(String type, boolean add) {
 
-        /*
-         * create a PDFStream with the next object number and add it
-         *
-         * to the list of objects
-         */
+        // create a PDFStream with the next object number 
+        // and add it to the list of objects
         PDFStream obj = new PDFStream(++this.objectcount);
         obj.addDefaultFilters(filterMap, type);
+        if (isEncryptionActive()) {
+            this.encryption.applyFilter(obj);
+        }
 
         if (add) {
             this.objects.add(obj);
@@ -1841,7 +1870,7 @@ public class PDFDocument {
      * @throws IOException if there is an exception writing to the output stream
      */
     public void outputHeader(OutputStream stream)
-    throws IOException {
+                throws IOException {
         this.position = 0;
 
         byte[] pdf = ("%PDF-" + PDF_VERSION + "\n").getBytes();
@@ -1864,7 +1893,7 @@ public class PDFDocument {
      * @throws IOException if there is an exception writing to the output stream
      */
     public void outputTrailer(OutputStream stream)
-    throws IOException {
+                throws IOException {
         output(stream);
         for (int count = 0; count < trailerObjects.size(); count++) {
             PDFObject o = (PDFObject) trailerObjects.get(count);
@@ -1876,13 +1905,21 @@ public class PDFDocument {
           by the table's length */
         this.position += outputXref(stream);
 
+        // Determine existance of encryption dictionary
+        String encryptEntry = "";
+        if (this.encryption != null) {
+            encryptEntry = this.encryption.getTrailerEntry();
+        }
+
         /* construct the trailer */
         String pdf = "trailer\n" + "<<\n"
                      + "/Size " + (this.objectcount + 1) + "\n"
                      + "/Root " + this.root.number + " "
-                     + this.root.generation + " R\n" + "/Info "
-                     + this.info.number + " " + this.info.generation
-                     + " R\n" + ">>\n" + "startxref\n" + this.xref
+                     + this.root.generation + " R\n" 
+                     + "/Info " + this.info.number + " " 
+                     + this.info.generation + " R\n" 
+                     + encryptEntry
+                     + ">>\n" + "startxref\n" + this.xref
                      + "\n" + "%%EOF\n";
 
         /* write the trailer */
diff --git a/src/java/org/apache/fop/pdf/PDFEncryption.java b/src/java/org/apache/fop/pdf/PDFEncryption.java
new file mode 100644 (file)
index 0000000..e0ad295
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * $Id$
+ * ============================================================================
+ *                    The Apache Software License, Version 1.1
+ * ============================================================================
+ * 
+ * Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 
+ * 3. The end-user documentation included with the redistribution, if any, must
+ *    include the following acknowledgment: "This product includes software
+ *    developed by the Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgment may appear in the software itself, if
+ *    and wherever such third-party acknowledgments normally appear.
+ * 
+ * 4. The names "FOP" and "Apache Software Foundation" must not be used to
+ *    endorse or promote products derived from this software without prior
+ *    written permission. For written permission, please contact
+ *    apache@apache.org.
+ * 
+ * 5. Products derived from this software may not be called "Apache", nor may
+ *    "Apache" appear in their name, without prior written permission of the
+ *    Apache Software Foundation.
+ * 
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU-
+ * DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ============================================================================
+ * 
+ * This software consists of voluntary contributions made by many individuals
+ * on behalf of the Apache Software Foundation and was originally created by
+ * James Tauber <jtauber@jtauber.com>. For more information on the Apache
+ * Software Foundation, please see <http://www.apache.org/>.
+ */ 
+package org.apache.fop.pdf;
+
+/**
+ * This interface defines the contract for classes implementing PDF encryption.
+ */
+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
+     */    
+    void applyFilter(PDFStream stream);
+    /**
+     * Returns the trailer entry for encryption.
+     * @return the trailer entry
+     */
+    String getTrailerEntry();
+}
diff --git a/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java b/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java
new file mode 100644 (file)
index 0000000..affcc2f
--- /dev/null
@@ -0,0 +1,432 @@
+/*
+ * $Id: PDFEncryptionJCE.java,v 1.1.2.1 2003/03/05 18:58:15 pietsch Exp $
+ * ============================================================================
+ *                    The Apache Software License, Version 1.1
+ * ============================================================================
+ * 
+ * Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 
+ * 3. The end-user documentation included with the redistribution, if any, must
+ *    include the following acknowledgment: "This product includes software
+ *    developed by the Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgment may appear in the software itself, if
+ *    and wherever such third-party acknowledgments normally appear.
+ * 
+ * 4. The names "FOP" and "Apache Software Foundation" must not be used to
+ *    endorse or promote products derived from this software without prior
+ *    written permission. For written permission, please contact
+ *    apache@apache.org.
+ * 
+ * 5. Products derived from this software may not be called "Apache", nor may
+ *    "Apache" appear in their name, without prior written permission of the
+ *    Apache Software Foundation.
+ * 
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU-
+ * DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ============================================================================
+ * 
+ * This software consists of voluntary contributions made by many individuals
+ * on behalf of the Apache Software Foundation and was originally created by
+ * James Tauber <jtauber@jtauber.com>. For more information on the Apache
+ * Software Foundation, please see <http://www.apache.org/>.
+ */ 
+package org.apache.fop.pdf;
+
+// Java
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.InvalidKeyException;
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.BadPaddingException;
+import javax.crypto.NoSuchPaddingException;
+
+import java.util.Random;
+
+/**
+ * class representing a /Filter /Standard object.
+ *
+ */
+public class PDFEncryptionJCE extends PDFObject implements PDFEncryption {
+    
+    private class EncryptionFilter extends PDFFilter {
+        private PDFEncryptionJCE encryption;
+        private int number;
+        private int generation;
+
+        /** 
+         * 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;
+        }
+
+        /** 
+         * Return a PDF string representation of the filter. In this
+         * case no filter name is passed.
+         * @return The filter name, blank in this case
+         */
+        public String getName() {
+            return "";
+        }
+
+        /** 
+         * Return a parameter dictionary for this filter, or null
+         * @return The parameter dictionary. In this case, null.
+         */
+        public String getDecodeParms() {
+            return null;
+        }
+
+        /** 
+         * Encode the given data with the filter
+         * @param data The data to be encrypted
+         * @return The encrypted data
+         */
+        public byte[] encode(byte[] data) {
+            return encryption.encryptData(data, number, generation);
+        }
+        
+        /**
+         * @see org.apache.fop.pdf.PDFFilter#encode(InputStream, OutputStream, int)
+         */
+        public void encode(InputStream in, OutputStream out, int length) 
+                                                        throws IOException {
+            byte[] buffer = new byte[length];
+            in.read(buffer);
+            buffer = encode(buffer);
+            out.write(buffer);
+        }
+        
+    }
+
+    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 };
+    private static final char[] DIGITS = 
+                                 {'0', '1', '2', '3', '4', '5', '6', '7',
+                                  '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+                                  
+    /** 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;
+
+    /**
+     * create a /Filter /Standard object.
+     *
+     * @param number the object's number
+     */
+    public PDFEncryptionJCE(int number) {
+        /* generic creation of object */
+        super(number);
+        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());
+        }
+    }
+
+    /**
+     * 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;
+    }
+
+
+    /**
+     * Returns the encryption parameters.
+     * @return the encryption parameters
+     */
+    public PDFEncryptionParams getParams() {
+        return this.params;
+    }
+
+    /**
+     * Sets the encryption parameters.
+     * @param params The parameterss to set
+     */
+    public void setParams(PDFEncryptionParams params) {
+        this.params = params;
+    }
+
+    // Internal procedures
+    
+    private byte[] prepPassword(String password) {
+        byte[] obuffer = new byte[32];
+        byte[] pbuffer = password.getBytes();
+
+        int i = 0;
+        int j = 0;
+        
+        while (i < obuffer.length && i < pbuffer.length) {
+            obuffer[i] = pbuffer[i];
+            i++;
+        }
+        while (i < obuffer.length) {
+            obuffer[i++] = (byte) PAD[j++];
+        }
+
+        return obuffer;
+    }
+
+    private String toHex(byte[] value) {
+        StringBuffer buffer = new StringBuffer();
+        
+        for (int i = 0; i < value.length; i++) {
+            buffer.append(DIGITS[(value[i] >>> 4) & 0x0F]);
+            buffer.append(DIGITS[value[i] & 0x0F]);
+        }
+        
+        return buffer.toString();
+    }
+    
+    /** 
+     * Returns the document file ID
+     * @return The file ID
+     */    
+    public byte[] getFileID() {
+        if (fileID == null) {
+            fileID = new byte[16];
+            random.nextBytes(fileID);
+        }
+        
+        return fileID;
+    }
+    
+    /** 
+     * 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 toHex(getFileID());
+        }
+        
+        byte[] id = new byte[16];
+        random.nextBytes(id);
+        return toHex(id);
+    }
+        
+    private byte[] encryptWithKey(byte[] data, byte[] key) {
+        try {
+            SecretKeySpec keyspec = new SecretKeySpec(key, "RC4");
+            cipher.init(Cipher.ENCRYPT_MODE, keyspec);
+            return cipher.doFinal(data);
+        } catch (IllegalBlockSizeException e) {
+            throw new IllegalStateException(e.getMessage());
+        } catch (BadPaddingException e) {
+            throw new IllegalStateException(e.getMessage());
+        } catch (InvalidKeyException e) {
+            throw new IllegalStateException(e.getMessage());
+        }
+    }
+    
+    private byte[] encryptWithHash(byte[] data, byte[] hash, int size) {
+        hash = digest.digest(hash);
+        
+        byte[] key = new byte[size];
+
+        for (int i = 0; i < size; i++) {
+            key[i] = hash[i];
+        }
+        
+        return encryptWithKey(data, key);
+    }
+
+    /** 
+     * 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 = this.number + " " + this.generation
+                        + " obj\n<< /Filter /Standard\n"
+                        + "/V 1\n"
+                        + "/R 2\n"
+                        + "/Length 40\n"
+                        + "/P "  + permissions + "\n"
+                        + "/O <" + toHex(oValue) + ">\n"
+                        + "/U <" + toHex(uValue) + ">\n"
+                        + ">>\n"
+                        + "endobj\n";
+    }
+
+    /** 
+     * 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
+     */    
+    public byte[] encryptData(byte[] data, int number, int generation) {
+        if (this.encryptionKey == null) {
+            throw new IllegalStateException("PDF Encryption has not been initialized");
+        }
+        
+        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 encryptWithHash(data, hash, hash.length);
+    }
+
+    /** 
+     * 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
+     */    
+    public void applyFilter(PDFStream stream) {
+        stream.addFilter(this.makeFilter(stream.number, stream.generation));
+    }
+    
+    /**
+     * Represent the object in PDF
+     *
+     * @return the PDF
+     */
+    public byte[] toPDF() {
+        if (this.dictionary == null) {
+            throw new IllegalStateException("PDF Encryption has not been initialized");
+        }
+        
+        try {
+            return this.dictionary.getBytes(PDFDocument.ENCODING);
+        } catch (UnsupportedEncodingException ue) {
+            return this.dictionary.getBytes();
+        }       
+    }
+
+    /**
+     * @see org.apache.fop.pdf.PDFEncryption#getTrailerEntry()
+     */
+    public String getTrailerEntry() {
+        return "/Encrypt " + number + " " 
+                    + generation + " R\n"
+                    + "/ID[<" + getFileID(1) + "><"
+                    + getFileID(2) + ">]\n";
+    }
+}
diff --git a/src/java/org/apache/fop/pdf/PDFEncryptionManager.java b/src/java/org/apache/fop/pdf/PDFEncryptionManager.java
new file mode 100644 (file)
index 0000000..cd87944
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+ * $Id$
+ * ============================================================================
+ *                    The Apache Software License, Version 1.1
+ * ============================================================================
+ * 
+ * Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 
+ * 3. The end-user documentation included with the redistribution, if any, must
+ *    include the following acknowledgment: "This product includes software
+ *    developed by the Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgment may appear in the software itself, if
+ *    and wherever such third-party acknowledgments normally appear.
+ * 
+ * 4. The names "FOP" and "Apache Software Foundation" must not be used to
+ *    endorse or promote products derived from this software without prior
+ *    written permission. For written permission, please contact
+ *    apache@apache.org.
+ * 
+ * 5. Products derived from this software may not be called "Apache", nor may
+ *    "Apache" appear in their name, without prior written permission of the
+ *    Apache Software Foundation.
+ * 
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU-
+ * DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ============================================================================
+ * 
+ * This software consists of voluntary contributions made by many individuals
+ * on behalf of the Apache Software Foundation and was originally created by
+ * James Tauber <jtauber@jtauber.com>. For more information on the Apache
+ * Software Foundation, please see <http://www.apache.org/>.
+ */ 
+package org.apache.fop.pdf;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.Provider;
+import java.security.Security;
+
+import org.apache.avalon.framework.logger.Logger;
+import org.apache.fop.fo.FOUserAgent;
+
+/**
+ * This class acts as a factory for PDF encryption support. It enables the
+ * feature to be optional to FOP depending on the availability of JCE.
+ */
+public class PDFEncryptionManager {
+
+    /**
+     * Indicates whether JCE is available.
+     * @return boolean true if JCE is present
+     */
+    public static boolean isJCEAvailable() {
+        try {
+            Class clazz = Class.forName("javax.crypto.Cipher");
+            return true;
+        } catch (ClassNotFoundException e) {
+            return false;
+        }
+    }
+    
+    /**
+     * Checks whether the necessary algorithms are available.
+     * @return boolean True if all necessary algorithms are present
+     */
+    public static boolean checkAvailableAlgorithms() {
+        if (!isJCEAvailable()) {
+            return false;
+        } else {
+            Provider[] providers;
+            providers = Security.getProviders("Cipher.RC4");
+            if (providers == null) {
+                return false;
+            }
+            providers = Security.getProviders("MessageDigest.MD5");
+            if (providers == null) {
+                return false;
+            }
+            return true;
+        }
+    }
+    
+
+    /**
+     * Sets up PDF encryption if PDF encryption is requested by registering
+     * a <code>PDFEncryptionParams</code> object with the user agent and if
+     * the necessary cryptographic support is available.
+     * @param userAgent the user agent
+     * @param pdf the PDF document to setup encryption for
+     * @param log the logger to send warnings to
+     */
+    public static void setupPDFEncryption(FOUserAgent userAgent, 
+                                          PDFDocument pdf,
+                                          Logger log) {
+        if (userAgent == null) {
+            throw new NullPointerException("User agent must not be null");
+        }
+        if (pdf == null) {
+            throw new NullPointerException("PDF document must not be null");
+        }
+        if (userAgent.getPDFEncryptionParams() != null) {
+            if (!checkAvailableAlgorithms()) {
+                if (isJCEAvailable()) {
+                    log.warn("PDF encryption has been requested, JCE is "
+                            + "available but there's no "
+                            + "JCE provider available that provides the "
+                            + "necessary algorithms. The PDF won't be "
+                            + "encrypted.");
+                } else {
+                    log.warn("PDF encryption has been requested but JCE is "
+                            + "unavailable! The PDF won't be encrypted.");
+                }
+            }
+            pdf.setEncryption(userAgent.getPDFEncryptionParams());
+        }
+    }
+    
+    /**
+     * Creates a new PDFEncryption instance if PDF encryption is available.
+     * @param objnum PDF object number
+     * @param params PDF encryption parameters
+     * @return PDFEncryption the newly created instance, null if PDF encryption
+     * is unavailable.
+     */
+    public static PDFEncryption newInstance(int objnum, PDFEncryptionParams params) {
+        try {
+            Class clazz = Class.forName("org.apache.fop.pdf.PDFEncryptionJCE");
+            Method makeMethod = clazz.getMethod("make", 
+                        new Class[] {int.class, PDFEncryptionParams.class});
+            Object obj = makeMethod.invoke(null, 
+                        new Object[] {new Integer(objnum), params});
+            return (PDFEncryption)obj;
+        } catch (ClassNotFoundException e) {
+            if (checkAvailableAlgorithms()) {
+                System.out.println("JCE and algorithms available, but the "
+                    + "implementation class unavailable. Please do a full "
+                    + "rebuild.");
+            }
+            return null;
+        } catch (NoSuchMethodException e) {
+            e.printStackTrace();
+            return null;
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+            return null;
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+    
+}
diff --git a/src/java/org/apache/fop/pdf/PDFEncryptionParams.java b/src/java/org/apache/fop/pdf/PDFEncryptionParams.java
new file mode 100644 (file)
index 0000000..ab92641
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ * $Id$
+ * ============================================================================
+ *                    The Apache Software License, Version 1.1
+ * ============================================================================
+ * 
+ * Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 
+ * 3. The end-user documentation included with the redistribution, if any, must
+ *    include the following acknowledgment: "This product includes software
+ *    developed by the Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgment may appear in the software itself, if
+ *    and wherever such third-party acknowledgments normally appear.
+ * 
+ * 4. The names "FOP" and "Apache Software Foundation" must not be used to
+ *    endorse or promote products derived from this software without prior
+ *    written permission. For written permission, please contact
+ *    apache@apache.org.
+ * 
+ * 5. Products derived from this software may not be called "Apache", nor may
+ *    "Apache" appear in their name, without prior written permission of the
+ *    Apache Software Foundation.
+ * 
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU-
+ * DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ============================================================================
+ * 
+ * This software consists of voluntary contributions made by many individuals
+ * on behalf of the Apache Software Foundation and was originally created by
+ * James Tauber <jtauber@jtauber.com>. For more information on the Apache
+ * Software Foundation, please see <http://www.apache.org/>.
+ */ 
+package org.apache.fop.pdf;
+
+/**
+ * This class holds the parameters for PDF encryption.
+ */
+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;
+    
+    /**
+     * Creates a new instance.
+     * @param userPassword the user password
+     * @param ownerPassword the owner password
+     * @param allowPrint true if printing is allowed
+     * @param allowCopyContent true if copying content is allowed
+     * @param allowEditContent true if editing content is allowed
+     * @param allowEditAnnotations true if editing annotations is allowed
+     */
+    public PDFEncryptionParams(String userPassword, String ownerPassword,
+                               boolean allowPrint,
+                               boolean allowCopyContent,
+                               boolean allowEditContent,
+                               boolean allowEditAnnotations) {
+        setUserPassword(userPassword);
+        setOwnerPassword(ownerPassword);
+        setAllowPrint(allowPrint);
+        setAllowCopyContent(allowCopyContent);
+        setAllowEditContent(allowEditContent);
+        setAllowEditAnnotations(allowEditAnnotations);
+    }
+    
+    /**
+     * Default constructor initializing to default values.
+     */
+    public PDFEncryptionParams() {
+        //nop
+    }
+     
+    /**
+     * Indicates whether copying content is allowed.
+     * @return true if copying is allowed
+     */
+    public boolean isAllowCopyContent() {
+        return allowCopyContent;
+    }
+
+    /**
+     * Indicates whether editing annotations is allowed.
+     * @return true is editing annotations is allowed
+     */
+    public boolean isAllowEditAnnotations() {
+        return allowEditAnnotations;
+    }
+
+    /**
+     * Indicates whether editing content is allowed.
+     * @return true if editing content is allowed
+     */
+    public boolean isAllowEditContent() {
+        return allowEditContent;
+    }
+
+    /**
+     * Indicates whether printing is allowed.
+     * @return true if printing is allowed
+     */
+    public boolean isAllowPrint() {
+        return allowPrint;
+    }
+
+    /**
+     * Returns the owner password.
+     * @return the owner password, an empty string if no password applies
+     */
+    public String getOwnerPassword() {
+        return ownerPassword;
+    }
+
+    /**
+     * Returns the user password.
+     * @return the user password, an empty string if no password applies
+     */
+    public String getUserPassword() {
+        return userPassword;
+    }
+
+    /**
+     * Sets the permission for copying content.
+     * @param allowCopyContent true if copying content is allowed
+     */
+    public void setAllowCopyContent(boolean allowCopyContent) {
+        this.allowCopyContent = allowCopyContent;
+    }
+
+    /**
+     * Sets the permission for editing annotations.
+     * @param allowEditAnnotations true if editing annotations is allowed
+     */
+    public void setAllowEditAnnotations(boolean allowEditAnnotations) {
+        this.allowEditAnnotations = allowEditAnnotations;
+    }
+
+    /**
+     * Sets the permission for editing content.
+     * @param allowEditContent true if editing annotations is allowed
+     */
+    public void setAllowEditContent(boolean allowEditContent) {
+        this.allowEditContent = allowEditContent;
+    }
+
+    /**
+     * Sets the persmission for printing.
+     * @param allowPrint true if printing is allowed
+     */
+    public void setAllowPrint(boolean allowPrint) {
+        this.allowPrint = allowPrint;
+    }
+
+    /**
+     * Sets the owner password.
+     * @param ownerPassword The owner password to set, null or an empty String
+     * if no password is applicable
+     */
+    public void setOwnerPassword(String ownerPassword) {
+        if (ownerPassword == null) {
+            this.ownerPassword = "";
+        } else {
+            this.ownerPassword = ownerPassword;
+        }
+    }
+
+    /**
+     * Sets the user password.
+     * @param userPassword The user password to set, null or an empty String
+     * if no password is applicable
+     */
+    public void setUserPassword(String userPassword) {
+        if (userPassword == null) {
+            this.userPassword = "";
+        } else {
+            this.userPassword = userPassword;
+        }
+    }
+
+}
index be3734964fbca39d15a5a733fe1951e6aa43509f..aff0e6d8f6f0486334074dc7f97defedf82342cd 100644 (file)
@@ -79,6 +79,7 @@ import org.apache.fop.fo.properties.RuleStyle;
 import org.apache.fop.fo.properties.BackgroundRepeat;
 import org.apache.fop.fonts.Font;
 import org.apache.fop.fonts.FontMetrics;
+import org.apache.fop.pdf.PDFEncryptionManager;
 import org.apache.fop.pdf.PDFStream;
 import org.apache.fop.pdf.PDFDocument;
 import org.apache.fop.pdf.PDFInfo;
@@ -240,6 +241,7 @@ public class PDFRenderer extends PrintRenderer {
      * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
      */
     public void configure(Configuration conf) throws ConfigurationException {
+        //PDF filters
         Configuration filters = conf.getChild("filterList");
         Configuration[] filt = filters.getChildren("value");
         List filterList = new java.util.ArrayList();
@@ -250,6 +252,7 @@ public class PDFRenderer extends PrintRenderer {
 
         filterMap.put(PDFStream.DEFAULT_FILTER, filterList);
 
+        //Font configuration
         Configuration[] font = conf.getChildren("font");
         for (int i = 0; i < font.length; i++) {
             Configuration[] triple = font[i].getChildren("font-triplet");
@@ -271,6 +274,7 @@ public class PDFRenderer extends PrintRenderer {
             fontList.add(efi);
         }
 
+
     }
 
     /**
@@ -311,7 +315,10 @@ public class PDFRenderer extends PrintRenderer {
         this.pdfDoc = new PDFDocument(producer);
         this.pdfDoc.setCreator(creator);
         this.pdfDoc.setFilterMap(filterMap);
-        pdfDoc.outputHeader(stream);
+        this.pdfDoc.outputHeader(stream);
+        
+        //Setup encryption if necessary
+        PDFEncryptionManager.setupPDFEncryption(userAgent, this.pdfDoc, getLogger());
     }
 
     /**