Browse Source

Added support for 128bit encryption in PDF output. Based on work by Michael Rubin.


git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1154998 13f79535-47bb-0310-9956-ffa450edef68
pull/21/head
Vincent Hennebert 12 years ago
parent
commit
c74764e436
28 changed files with 1786 additions and 389 deletions
  1. 7
    1
      build.xml
  2. 5
    0
      src/documentation/content/xdocs/trunk/configuration.xml
  3. 78
    5
      src/documentation/content/xdocs/trunk/pdfencryption.xml
  4. 4
    0
      src/documentation/content/xdocs/trunk/running.xml
  5. 16
    0
      src/java/org/apache/fop/cli/CommandLineOptions.java
  6. 124
    0
      src/java/org/apache/fop/pdf/FileIDGenerator.java
  7. 21
    27
      src/java/org/apache/fop/pdf/PDFDocument.java
  8. 0
    12
      src/java/org/apache/fop/pdf/PDFEncryption.java
  9. 439
    291
      src/java/org/apache/fop/pdf/PDFEncryptionJCE.java
  10. 6
    4
      src/java/org/apache/fop/pdf/PDFEncryptionManager.java
  11. 108
    1
      src/java/org/apache/fop/pdf/PDFEncryptionParams.java
  12. 1
    1
      src/java/org/apache/fop/pdf/PDFObject.java
  13. 30
    0
      src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java
  14. 10
    0
      src/java/org/apache/fop/render/pdf/PDFEventProducer.java
  15. 1
    0
      src/java/org/apache/fop/render/pdf/PDFEventProducer.xml
  16. 45
    2
      src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java
  17. 45
    45
      src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java
  18. 3
    0
      status.xml
  19. 4
    0
      test/java/org/apache/fop/UtilityCodeTestSuite.java
  20. 106
    0
      test/java/org/apache/fop/pdf/FileIDGeneratorTestCase.java
  21. 492
    0
      test/java/org/apache/fop/pdf/PDFEncryptionJCETestCase.java
  22. 146
    0
      test/java/org/apache/fop/render/pdf/PDFRendererConfiguratorTestCase.java
  23. 45
    0
      test/java/org/apache/fop/render/pdf/RenderPDFTestSuite.java
  24. 10
    0
      test/resources/org/apache/fop/render/pdf/above128.xconf
  25. 10
    0
      test/resources/org/apache/fop/render/pdf/below40.xconf
  26. 10
    0
      test/resources/org/apache/fop/render/pdf/correct.xconf
  27. 10
    0
      test/resources/org/apache/fop/render/pdf/roundDown.xconf
  28. 10
    0
      test/resources/org/apache/fop/render/pdf/roundUp.xconf

+ 7
- 1
build.xml View File

@@ -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>

+ 5
- 0
src/documentation/content/xdocs/trunk/configuration.xml View File

@@ -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>

+ 78
- 5
src/documentation/content/xdocs/trunk/pdfencryption.xml View File

@@ -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>

+ 4
- 0
src/documentation/content/xdocs/trunk/running.xml View File

@@ -128,6 +128,10 @@ Fop [options] [-fo|-xml] infile [-xsl file] [-awt|-pdf|-mif|-rtf|-tiff|-png|-pcl
-nocopy PDF file will be encrypted without copy content permission
-noedit PDF file will be encrypted without edit content permission
-noannotations PDF file will be encrypted without edit annotation permission
-nofillinforms PDF file will be encrypted without fill in forms permission
-noaccesscontent PDF file will be encrypted without extract text and graphics permission
-noassembledoc PDF file will be encrypted without assemble the document permission
-noprinthq PDF file will be encrypted without print high quality permission
-a enables accessibility features (Tagged PDF etc., default off)
-pdfprofile prof PDF file will be generated with the specified profile
(Examples for prof: PDF/A-1b or PDF/X-3:2003)

+ 16
- 0
src/java/org/apache/fop/cli/CommandLineOptions.java View File

@@ -381,6 +381,14 @@ public class CommandLineOptions {
getPDFEncryptionParams().setAllowEditContent(false);
} else if (args[i].equals("-noannotations")) {
getPDFEncryptionParams().setAllowEditAnnotations(false);
} else if (args[i].equals("-nofillinforms")) {
getPDFEncryptionParams().setAllowFillInForms(false);
} else if (args[i].equals("-noaccesscontent")) {
getPDFEncryptionParams().setAllowAccessContent(false);
} else if (args[i].equals("-noassembledoc")) {
getPDFEncryptionParams().setAllowAssembleDocument(false);
} else if (args[i].equals("-noprinthq")) {
getPDFEncryptionParams().setAllowPrintHq(false);
} else if (args[i].equals("-version")) {
printVersion();
return false;
@@ -1181,6 +1189,14 @@ public class CommandLineOptions {
+ " -nocopy PDF file will be encrypted without copy content permission\n"
+ " -noedit PDF file will be encrypted without edit content permission\n"
+ " -noannotations PDF file will be encrypted without edit annotation permission\n"
+ " -nofillinforms PDF file will be encrypted without"
+ " fill in interactive form fields permission\n"
+ " -noaccesscontent PDF file will be encrypted without"
+ " extract text and graphics permission\n"
+ " -noassembledoc PDF file will be encrypted without"
+ " assemble the document permission\n"
+ " -noprinthq PDF file will be encrypted without"
+ " print high quality permission\n"
+ " -a enables accessibility features (Tagged PDF etc., default off)\n"
+ " -pdfprofile prof PDF file will be generated with the specified profile\n"
+ " (Examples for prof: PDF/A-1b or PDF/X-3:2003)\n\n"

+ 124
- 0
src/java/org/apache/fop/pdf/FileIDGenerator.java View File

@@ -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);
}
}

+ 21
- 27
src/java/org/apache/fop/pdf/PDFDocument.java View File

@@ -25,10 +25,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
@@ -186,6 +183,8 @@ public class PDFDocument {

private boolean encodingOnTheFly = true;

private FileIDGenerator fileIDGenerator;

/**
* Creates an empty PDF document.
*
@@ -513,10 +512,10 @@ public class PDFDocument {
*/
public void setEncryption(PDFEncryptionParams params) {
getProfile().verifyEncryptionAllowed();
this.encryption = PDFEncryptionManager.newInstance(++this.objectcount, params);
fileIDGenerator = FileIDGenerator.getRandomFileIDGenerator();
this.encryption = PDFEncryptionManager.newInstance(++this.objectcount, params, this);
if (this.encryption != null) {
PDFObject pdfObject = (PDFObject)this.encryption;
pdfObject.setDocument(this);
addTrailerObject(pdfObject);
} else {
log.warn(
@@ -979,27 +978,6 @@ public class PDFDocument {
this.position += bin.length;
}

/** @return the "ID" entry for the file trailer */
protected String getIDEntry() {
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
DateFormat df = new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS");
digest.update(encode(df.format(new Date())));
//Ignoring the filename here for simplicity even though it's recommended by the PDF spec
digest.update(encode(String.valueOf(this.position)));
digest.update(getInfo().toPDF());
byte[] res = digest.digest();
String s = PDFText.toHex(res);
return "/ID [" + s + " " + s + "]";
} catch (NoSuchAlgorithmException e) {
if (getProfile().isIDEntryRequired()) {
throw new UnsupportedOperationException("MD5 not available: " + e.getMessage());
} else {
return ""; //Entry is optional if PDF/A or PDF/X are not active
}
}
}

/**
* Write the trailer
*
@@ -1038,7 +1016,9 @@ public class PDFDocument {
if (this.isEncryptionActive()) {
pdf.append(this.encryption.getTrailerEntry());
} else {
pdf.append(this.getIDEntry());
byte[] fileID = getFileIDGenerator().getOriginalFileID();
String fileIDAsString = PDFText.toHex(fileID);
pdf.append("/ID [" + fileIDAsString + " " + fileIDAsString + "]");
}

pdf.append("\n>>\nstartxref\n")
@@ -1089,4 +1069,18 @@ public class PDFDocument {
return pdfBytes.length;
}

long getCurrentFileSize() {
return position;
}

FileIDGenerator getFileIDGenerator() {
if (fileIDGenerator == null) {
try {
fileIDGenerator = FileIDGenerator.getDigestFileIDGenerator(this);
} catch (NoSuchAlgorithmException e) {
fileIDGenerator = FileIDGenerator.getRandomFileIDGenerator();
}
}
return fileIDGenerator;
}
}

+ 0
- 12
src/java/org/apache/fop/pdf/PDFEncryption.java View File

@@ -24,18 +24,6 @@ package org.apache.fop.pdf;
*/
public interface PDFEncryption {

/**
* Returns the encryption parameters.
* @return the encryption parameters
*/
PDFEncryptionParams getParams();

/**
* Sets the encryption parameters.
* @param params The parameterss to set
*/
void setParams(PDFEncryptionParams params);

/**
* Adds a PDFFilter to the PDFStream object
* @param stream the stream to add an encryption filter to

+ 439
- 291
src/java/org/apache/fop/pdf/PDFEncryptionJCE.java View File

@@ -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;
}

}

+ 6
- 4
src/java/org/apache/fop/pdf/PDFEncryptionManager.java View File

@@ -109,16 +109,18 @@ public final class PDFEncryptionManager {
* Creates a new PDFEncryption instance if PDF encryption is available.
* @param objnum PDF object number
* @param params PDF encryption parameters
* @param pdf the PDF document to encrypt
* @return PDFEncryption the newly created instance, null if PDF encryption
* is unavailable.
*/
public static PDFEncryption newInstance(int objnum, PDFEncryptionParams params) {
public static PDFEncryption newInstance(int objnum, PDFEncryptionParams params,
PDFDocument pdf) {
try {
Class clazz = Class.forName("org.apache.fop.pdf.PDFEncryptionJCE");
Class<?> clazz = Class.forName("org.apache.fop.pdf.PDFEncryptionJCE");
Method makeMethod = clazz.getMethod("make",
new Class[] {int.class, PDFEncryptionParams.class});
new Class[] {int.class, PDFEncryptionParams.class, PDFDocument.class});
Object obj = makeMethod.invoke(null,
new Object[] {new Integer(objnum), params});
new Object[] {new Integer(objnum), params, pdf});
return (PDFEncryption)obj;
} catch (ClassNotFoundException e) {
if (checkAvailableAlgorithms()) {

+ 108
- 1
src/java/org/apache/fop/pdf/PDFEncryptionParams.java View File

@@ -26,10 +26,17 @@ public class PDFEncryptionParams {

private String userPassword = ""; //May not be null
private String ownerPassword = ""; //May not be null

private boolean allowPrint = true;
private boolean allowCopyContent = true;
private boolean allowEditContent = true;
private boolean allowEditAnnotations = true;
private boolean allowFillInForms = true;
private boolean allowAccessContent = true;
private boolean allowAssembleDocument = true;
private boolean allowPrintHq = true;

private int encryptionLengthInBits = 40;

/**
* Creates a new instance.
@@ -60,6 +67,25 @@ public class PDFEncryptionParams {
//nop
}

/**
* Creates a copy of the given encryption parameters.
*
* @param source source encryption parameters
*/
public PDFEncryptionParams(PDFEncryptionParams source) {
setUserPassword(source.getUserPassword());
setOwnerPassword(source.getOwnerPassword());
setAllowPrint(source.isAllowPrint());
setAllowCopyContent(source.isAllowCopyContent());
setAllowEditContent(source.isAllowEditContent());
setAllowEditAnnotations(source.isAllowEditAnnotations());
setAllowAssembleDocument(source.isAllowAssembleDocument());
setAllowAccessContent(source.isAllowAccessContent());
setAllowFillInForms(source.isAllowFillInForms());
setAllowPrintHq(source.isAllowPrintHq());
setEncryptionLengthInBits(source.getEncryptionLengthInBits());
}

/**
* Indicates whether copying content is allowed.
* @return true if copying is allowed
@@ -92,6 +118,38 @@ public class PDFEncryptionParams {
return allowPrint;
}

/**
* Indicates whether revision 3 filling in forms is allowed.
* @return true if revision 3 filling in forms is allowed
*/
public boolean isAllowFillInForms() {
return allowFillInForms;
}

/**
* Indicates whether revision 3 extracting text and graphics is allowed.
* @return true if revision 3 extracting text and graphics is allowed
*/
public boolean isAllowAccessContent() {
return allowAccessContent;
}

/**
* Indicates whether revision 3 assembling document is allowed.
* @return true if revision 3 assembling document is allowed
*/
public boolean isAllowAssembleDocument() {
return allowAssembleDocument;
}

/**
* Indicates whether revision 3 printing to high quality is allowed.
* @return true if revision 3 printing to high quality is allowed
*/
public boolean isAllowPrintHq() {
return allowPrintHq;
}

/**
* Returns the owner password.
* @return the owner password, an empty string if no password applies
@@ -133,13 +191,45 @@ public class PDFEncryptionParams {
}

/**
* Sets the persmission for printing.
* Sets the permission for printing.
* @param allowPrint true if printing is allowed
*/
public void setAllowPrint(boolean allowPrint) {
this.allowPrint = allowPrint;
}

/**
* Sets whether revision 3 filling in forms is allowed.
* @param allowFillInForms true if revision 3 filling in forms is allowed.
*/
public void setAllowFillInForms(boolean allowFillInForms) {
this.allowFillInForms = allowFillInForms;
}

/**
* Sets whether revision 3 extracting text and graphics is allowed.
* @param allowAccessContent true if revision 3 extracting text and graphics is allowed
*/
public void setAllowAccessContent(boolean allowAccessContent) {
this.allowAccessContent = allowAccessContent;
}

/**
* Sets whether revision 3 assembling document is allowed.
* @param allowAssembleDocument true if revision 3 assembling document is allowed
*/
public void setAllowAssembleDocument(boolean allowAssembleDocument) {
this.allowAssembleDocument = allowAssembleDocument;
}

/**
* Sets whether revision 3 printing to high quality is allowed.
* @param allowPrintHq true if revision 3 printing to high quality is allowed
*/
public void setAllowPrintHq(boolean allowPrintHq) {
this.allowPrintHq = allowPrintHq;
}

/**
* Sets the owner password.
* @param ownerPassword The owner password to set, null or an empty String
@@ -166,4 +256,21 @@ public class PDFEncryptionParams {
}
}

/**
* Returns the encryption length.
* @return the encryption length
*/
public int getEncryptionLengthInBits() {
return encryptionLengthInBits;
}

/**
* Sets the encryption length.
*
* @param encryptionLength the encryption length
*/
public void setEncryptionLengthInBits(int encryptionLength) {
this.encryptionLengthInBits = encryptionLength;
}

}

+ 1
- 1
src/java/org/apache/fop/pdf/PDFObject.java View File

@@ -112,7 +112,7 @@ public abstract class PDFObject implements PDFWritable {
}

/**
* Returns the object's generation.
* Returns this object's generation.
* @return the PDF Object generation
*/
public int getGeneration() {

+ 30
- 0
src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java View File

@@ -38,6 +38,36 @@ public interface PDFConfigurationConstants {
String NO_EDIT_CONTENT = "noedit";
/** PDF encryption parameter: Forbids annotations, datatype: Boolean or "true"/"false" */
String NO_ANNOTATIONS = "noannotations";

/**
* PDF encryption parameter: Forbids filling in existing interactive forms, datatype:
* Boolean or "true"/"false"
*/
String NO_FILLINFORMS = "nofillinforms";

/**
* PDF encryption parameter: Forbids extracting text and graphics, datatype: Boolean
* or "true"/"false"
*/
String NO_ACCESSCONTENT = "noaccesscontent";

/**
* PDF encryption parameter: Forbids assembling document, datatype: Boolean or
* "true"/"false"
*/
String NO_ASSEMBLEDOC = "noassembledoc";

/**
* PDF encryption parameter: Forbids printing to high quality, datatype: Boolean or
* "true"/"false"
*/
String NO_PRINTHQ = "noprinthq";

/**
* PDF encryption length parameter: must be a multiple of 8 between 40 and 128,
* default value 40, datatype: int.
*/
String ENCRYPTION_LENGTH = "encryption-length";
/** Rendering Options key for the PDF/A mode. */
String PDF_A_MODE = "pdf-a-mode";
/** Rendering Options key for the PDF/X mode. */

+ 10
- 0
src/java/org/apache/fop/render/pdf/PDFEventProducer.java View File

@@ -65,4 +65,14 @@ public interface PDFEventProducer extends EventProducer {
* @event.severity WARN
*/
void nonStandardStructureType(Object source, String fo, String type, String fallback);

/**
* The encryption length must be a multiple of 8 between 40 and 128.
*
* @param source the event source
* @param originalValue requested encryption length
* @param correctedValue corrected encryption length
* @event.severity WARN
*/
void incorrectEncryptionLength(Object source, int originalValue, int correctedValue);
}

+ 1
- 0
src/java/org/apache/fop/render/pdf/PDFEventProducer.xml View File

@@ -2,4 +2,5 @@
<catalogue xml:lang="en">
<message key="nonFullyResolvedLinkTargets">{count} link target{count,equals,1,,s} could not be fully resolved and now point{count,equals,1,,s} to the top of the page or {count,equals,1,is,are} dysfunctional.</message>
<message key="nonStandardStructureType">‘{type}’ is not a standard structure type defined by the PDF Reference. Falling back to ‘{fallback}’.</message>
<message key="incorrectEncryptionLength">Encryption length must be a multiple of 8 between 40 and 128. Setting encryption length to {correctedValue} instead of {originalValue}.</message>
</catalogue>

+ 45
- 2
src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java View File

@@ -81,7 +81,7 @@ public class PDFRendererConfigurator extends PrintRendererConfigurator {
Configuration encryptionParamsConfig
= cfg.getChild(PDFConfigurationConstants.ENCRYPTION_PARAMS, false);
if (encryptionParamsConfig != null) {
PDFEncryptionParams encryptionParams = new PDFEncryptionParams();
PDFEncryptionParams encryptionParams = pdfUtil.getEncryptionParams();
Configuration ownerPasswordConfig = encryptionParamsConfig.getChild(
PDFConfigurationConstants.OWNER_PASSWORD, false);
if (ownerPasswordConfig != null) {
@@ -118,8 +118,35 @@ public class PDFRendererConfigurator extends PrintRendererConfigurator {
if (noAnnotationsConfig != null) {
encryptionParams.setAllowEditAnnotations(false);
}
pdfUtil.setEncryptionParams(encryptionParams);
Configuration noFillInForms = encryptionParamsConfig.getChild(
PDFConfigurationConstants.NO_FILLINFORMS, false);
if (noFillInForms != null) {
encryptionParams.setAllowFillInForms(false);
}
Configuration noAccessContentConfig = encryptionParamsConfig.getChild(
PDFConfigurationConstants.NO_ACCESSCONTENT, false);
if (noAccessContentConfig != null) {
encryptionParams.setAllowAccessContent(false);
}
Configuration noAssembleDocConfig = encryptionParamsConfig.getChild(
PDFConfigurationConstants.NO_ASSEMBLEDOC, false);
if (noAssembleDocConfig != null) {
encryptionParams.setAllowAssembleDocument(false);
}
Configuration noPrintHqConfig = encryptionParamsConfig.getChild(
PDFConfigurationConstants.NO_PRINTHQ, false);
if (noPrintHqConfig != null) {
encryptionParams.setAllowPrintHq(false);
}
Configuration encryptionLengthConfig = encryptionParamsConfig.getChild(
PDFConfigurationConstants.ENCRYPTION_LENGTH, false);
if (encryptionLengthConfig != null) {
int encryptionLength = checkEncryptionLength(
Integer.parseInt(encryptionLengthConfig.getValue(null)));
encryptionParams.setEncryptionLengthInBits(encryptionLength);
}
}

s = cfg.getChild(PDFConfigurationConstants.KEY_OUTPUT_PROFILE, true).getValue(null);
if (s != null) {
pdfUtil.setOutputProfileURI(s);
@@ -132,6 +159,22 @@ public class PDFRendererConfigurator extends PrintRendererConfigurator {
}
}

private int checkEncryptionLength(int encryptionLength) {
int correctEncryptionLength = encryptionLength;
if (encryptionLength < 40) {
correctEncryptionLength = 40;
} else if (encryptionLength > 128) {
correctEncryptionLength = 128;
} else if (encryptionLength % 8 != 0) {
correctEncryptionLength = ((int) Math.round(encryptionLength / 8.0f)) * 8;
}
if (correctEncryptionLength != encryptionLength) {
PDFEventProducer.Provider.get(userAgent.getEventBroadcaster())
.incorrectEncryptionLength(this, encryptionLength, correctEncryptionLength);
}
return correctEncryptionLength;
}

/**
* Builds a filter map from an Avalon Configuration object.
*

+ 45
- 45
src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java View File

@@ -124,49 +124,45 @@ class PDFRenderingUtil implements PDFConfigurationConstants {
if (params != null) {
this.encryptionParams = params; //overwrite if available
}
String pwd;
pwd = (String)userAgent.getRendererOptions().get(USER_PASSWORD);
if (pwd != null) {
if (encryptionParams == null) {
this.encryptionParams = new PDFEncryptionParams();
}
this.encryptionParams.setUserPassword(pwd);
String userPassword = (String)userAgent.getRendererOptions().get(USER_PASSWORD);
if (userPassword != null) {
getEncryptionParams().setUserPassword(userPassword);
}
pwd = (String)userAgent.getRendererOptions().get(OWNER_PASSWORD);
if (pwd != null) {
if (encryptionParams == null) {
this.encryptionParams = new PDFEncryptionParams();
}
this.encryptionParams.setOwnerPassword(pwd);
String ownerPassword = (String)userAgent.getRendererOptions().get(OWNER_PASSWORD);
if (ownerPassword != null) {
getEncryptionParams().setOwnerPassword(ownerPassword);
}
Object setting;
setting = userAgent.getRendererOptions().get(NO_PRINT);
if (setting != null) {
if (encryptionParams == null) {
this.encryptionParams = new PDFEncryptionParams();
}
this.encryptionParams.setAllowPrint(!booleanValueOf(setting));
Object noPrint = userAgent.getRendererOptions().get(NO_PRINT);
if (noPrint != null) {
getEncryptionParams().setAllowPrint(!booleanValueOf(noPrint));
}
setting = userAgent.getRendererOptions().get(NO_COPY_CONTENT);
if (setting != null) {
if (encryptionParams == null) {
this.encryptionParams = new PDFEncryptionParams();
}
this.encryptionParams.setAllowCopyContent(!booleanValueOf(setting));
Object noCopyContent = userAgent.getRendererOptions().get(NO_COPY_CONTENT);
if (noCopyContent != null) {
getEncryptionParams().setAllowCopyContent(!booleanValueOf(noCopyContent));
}
setting = userAgent.getRendererOptions().get(NO_EDIT_CONTENT);
if (setting != null) {
if (encryptionParams == null) {
this.encryptionParams = new PDFEncryptionParams();
}
this.encryptionParams.setAllowEditContent(!booleanValueOf(setting));
Object noEditContent = userAgent.getRendererOptions().get(NO_EDIT_CONTENT);
if (noEditContent != null) {
getEncryptionParams().setAllowEditContent(!booleanValueOf(noEditContent));
}
setting = userAgent.getRendererOptions().get(NO_ANNOTATIONS);
if (setting != null) {
if (encryptionParams == null) {
this.encryptionParams = new PDFEncryptionParams();
}
this.encryptionParams.setAllowEditAnnotations(!booleanValueOf(setting));
Object noAnnotations = userAgent.getRendererOptions().get(NO_ANNOTATIONS);
if (noAnnotations != null) {
getEncryptionParams().setAllowEditAnnotations(!booleanValueOf(noAnnotations));
}
Object noFillInForms = userAgent.getRendererOptions().get(NO_FILLINFORMS);
if (noFillInForms != null) {
getEncryptionParams().setAllowFillInForms(!booleanValueOf(noFillInForms));
}
Object noAccessContent = userAgent.getRendererOptions().get(NO_ACCESSCONTENT);
if (noAccessContent != null) {
getEncryptionParams().setAllowAccessContent(!booleanValueOf(noAccessContent));
}
Object noAssembleDoc = userAgent.getRendererOptions().get(NO_ASSEMBLEDOC);
if (noAssembleDoc != null) {
getEncryptionParams().setAllowAssembleDocument(!booleanValueOf(noAssembleDoc));
}
Object noPrintHQ = userAgent.getRendererOptions().get(NO_PRINTHQ);
if (noPrintHQ != null) {
getEncryptionParams().setAllowPrintHq(!booleanValueOf(noPrintHQ));
}
String s = (String)userAgent.getRendererOptions().get(PDF_A_MODE);
if (s != null) {
@@ -184,9 +180,10 @@ class PDFRenderingUtil implements PDFConfigurationConstants {
if (s != null) {
this.outputProfileURI = s;
}
setting = userAgent.getRendererOptions().get(KEY_DISABLE_SRGB_COLORSPACE);
if (setting != null) {
this.disableSRGBColorSpace = booleanValueOf(setting);
Object disableSRGBColorSpace = userAgent.getRendererOptions().get(
KEY_DISABLE_SRGB_COLORSPACE);
if (disableSRGBColorSpace != null) {
this.disableSRGBColorSpace = booleanValueOf(disableSRGBColorSpace);
}
}

@@ -236,11 +233,14 @@ class PDFRenderingUtil implements PDFConfigurationConstants {
}

/**
* Sets the encryption parameters used by the PDF renderer.
* @param encryptionParams the encryption parameters
* Gets the encryption parameters used by the PDF renderer.
* @return encryptionParams the encryption parameters
*/
public void setEncryptionParams(PDFEncryptionParams encryptionParams) {
this.encryptionParams = encryptionParams;
PDFEncryptionParams getEncryptionParams() {
if (this.encryptionParams == null) {
this.encryptionParams = new PDFEncryptionParams();
}
return this.encryptionParams;
}

private void updateInfo() {

+ 3
- 0
status.xml View File

@@ -60,6 +60,9 @@
documents. Example: the fix of marks layering will be such a case when it's done.
-->
<release version="FOP Trunk" date="TBD">
<action context="Renderers" dev="VH" type="add">
Added support for 128bit encryption in PDF output. Based on work by Michael Rubin.
</action>
<action context="Renderers" dev="PH" type="fix">
Fixed a bug in AFP where the object area axes of an Include Object was incorrectly set when
rotated by 180. </action>

+ 4
- 0
test/java/org/apache/fop/UtilityCodeTestSuite.java View File

@@ -23,6 +23,8 @@ import junit.framework.Test;
import junit.framework.TestSuite;

import org.apache.fop.events.BasicEventTestCase;
import org.apache.fop.pdf.FileIDGeneratorTestCase;
import org.apache.fop.pdf.PDFEncryptionJCETestCase;
import org.apache.fop.pdf.PDFObjectTestCase;
import org.apache.fop.traits.BorderPropsTestCase;
import org.apache.fop.util.BitmapImageUtilTestCase;
@@ -46,6 +48,7 @@ public class UtilityCodeTestSuite {
//$JUnit-BEGIN$
suite.addTest(new TestSuite(PDFNumberTestCase.class));
suite.addTest(new TestSuite(PDFObjectTestCase.class));
suite.addTest(FileIDGeneratorTestCase.suite());
suite.addTest(new TestSuite(ColorUtilTestCase.class));
suite.addTest(new TestSuite(BorderPropsTestCase.class));
suite.addTest(new TestSuite(ElementListUtilsTestCase.class));
@@ -53,6 +56,7 @@ public class UtilityCodeTestSuite {
suite.addTest(new TestSuite(XMLResourceBundleTestCase.class));
suite.addTest(new TestSuite(URIResolutionTestCase.class));
suite.addTest(new TestSuite(BitmapImageUtilTestCase.class));
suite.addTest(new TestSuite(PDFEncryptionJCETestCase.class));
//$JUnit-END$
return suite;
}

+ 106
- 0
test/java/org/apache/fop/pdf/FileIDGeneratorTestCase.java View File

@@ -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"));
}

}

}

+ 492
- 0
test/java/org/apache/fop/pdf/PDFEncryptionJCETestCase.java View File

@@ -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);
}

}

+ 146
- 0
test/java/org/apache/fop/render/pdf/PDFRendererConfiguratorTestCase.java View File

@@ -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());
}
}

+ 45
- 0
test/java/org/apache/fop/render/pdf/RenderPDFTestSuite.java View File

@@ -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;
}
}

+ 10
- 0
test/resources/org/apache/fop/render/pdf/above128.xconf View File

@@ -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>

+ 10
- 0
test/resources/org/apache/fop/render/pdf/below40.xconf View File

@@ -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>

+ 10
- 0
test/resources/org/apache/fop/render/pdf/correct.xconf View File

@@ -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>

+ 10
- 0
test/resources/org/apache/fop/render/pdf/roundDown.xconf View File

@@ -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>

+ 10
- 0
test/resources/org/apache/fop/render/pdf/roundUp.xconf View File

@@ -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>

Loading…
Cancel
Save