From 59e232cb5a6b5110d9f3d7efc994f6f59b966ebd Mon Sep 17 00:00:00 2001 From: Jeremias Maerki Date: Thu, 13 Mar 2003 16:46:05 +0000 Subject: [PATCH] Added support for PDF encryption. Submitted by: Patrick C. Lankswert 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 | 21 + src/java/org/apache/fop/pdf/PDFDocument.java | 59 ++- .../org/apache/fop/pdf/PDFEncryption.java | 81 ++++ .../org/apache/fop/pdf/PDFEncryptionJCE.java | 432 ++++++++++++++++++ .../apache/fop/pdf/PDFEncryptionManager.java | 170 +++++++ .../apache/fop/pdf/PDFEncryptionParams.java | 200 ++++++++ .../apache/fop/render/pdf/PDFRenderer.java | 9 +- 7 files changed, 960 insertions(+), 12 deletions(-) create mode 100644 src/java/org/apache/fop/pdf/PDFEncryption.java create mode 100644 src/java/org/apache/fop/pdf/PDFEncryptionJCE.java create mode 100644 src/java/org/apache/fop/pdf/PDFEncryptionManager.java create mode 100644 src/java/org/apache/fop/pdf/PDFEncryptionParams.java diff --git a/src/java/org/apache/fop/fo/FOUserAgent.java b/src/java/org/apache/fop/fo/FOUserAgent.java index b35fd6426..713e4c535 100644 --- a/src/java/org/apache/fop/fo/FOUserAgent.java +++ b/src/java/org/apache/fop/fo/FOUserAgent.java @@ -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); } } + } diff --git a/src/java/org/apache/fop/pdf/PDFDocument.java b/src/java/org/apache/fop/pdf/PDFDocument.java index 5e9c24d79..26b356c23 100644 --- a/src/java/org/apache/fop/pdf/PDFDocument.java +++ b/src/java/org/apache/fop/pdf/PDFDocument.java @@ -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 index 000000000..e0ad29552 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFEncryption.java @@ -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 . For more information on the Apache + * Software Foundation, please see . + */ +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 index 000000000..affcc2f68 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java @@ -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 . For more information on the Apache + * Software Foundation, please see . + */ +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 index 000000000..cd8794474 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFEncryptionManager.java @@ -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 . For more information on the Apache + * Software Foundation, please see . + */ +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 PDFEncryptionParams 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 index 000000000..ab9264172 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFEncryptionParams.java @@ -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 . For more information on the Apache + * Software Foundation, please see . + */ +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; + } + } + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderer.java b/src/java/org/apache/fop/render/pdf/PDFRenderer.java index be3734964..aff0e6d8f 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderer.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderer.java @@ -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()); } /** -- 2.39.5