git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1154998 13f79535-47bb-0310-9956-ffa450edef68pull/21/head
@@ -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> |
@@ -367,12 +367,17 @@ | |||
<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> | |||
@@ -69,10 +69,45 @@ | |||
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> | |||
@@ -88,6 +123,12 @@ | |||
<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> | |||
@@ -114,7 +155,7 @@ | |||
</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> | |||
@@ -124,6 +165,30 @@ | |||
<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> |
@@ -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) |
@@ -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" |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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 |
@@ -19,13 +19,12 @@ | |||
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; | |||
} | |||
} |
@@ -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()) { |
@@ -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; | |||
} | |||
} |
@@ -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() { |
@@ -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. */ |
@@ -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); | |||
} |
@@ -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> |
@@ -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. | |||
* |
@@ -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() { |
@@ -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> |
@@ -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; | |||
} |
@@ -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")); | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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()); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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> |