git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1154998 13f79535-47bb-0310-9956-ffa450edef68pull/21/head
<test name="org.apache.fop.text.linebreak.LineBreakStatusTest" todir="${junit.reports.dir}"/> | <test name="org.apache.fop.text.linebreak.LineBreakStatusTest" todir="${junit.reports.dir}"/> | ||||
</junit> | </junit> | ||||
</target> | </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-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"> | <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> | <fail><condition><or><isset property="fop.junit.error"/><isset property="fop.junit.failure"/><not><isset property="hyphenation.present"/></not></or></condition> |
<source><![CDATA[ | <source><![CDATA[ | ||||
<renderer mime="application/pdf"> | <renderer mime="application/pdf"> | ||||
<encryption-params> | <encryption-params> | ||||
<encryption-length>128</encryption-length> | |||||
<user-password>testuserpass</user-password> | <user-password>testuserpass</user-password> | ||||
<owner-password>testownerpass</owner-password> | <owner-password>testownerpass</owner-password> | ||||
<noprint/> | <noprint/> | ||||
<nocopy/> | <nocopy/> | ||||
<noedit/> | <noedit/> | ||||
<noannotations/> | <noannotations/> | ||||
<nofillinforms/> | |||||
<noaccesscontent/> | |||||
<noassembledoc/> | |||||
<noprinthq/> | |||||
</encryption-params> | </encryption-params> | ||||
</renderer>]]></source> | </renderer>]]></source> | ||||
supplied, viewing the content is not restricted. | supplied, viewing the content is not restricted. | ||||
</p> | </p> | ||||
<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> | </p> | ||||
</section> | </section> | ||||
<section> | <section> | ||||
<th>Values</th> | <th>Values</th> | ||||
<th>Default</th> | <th>Default</th> | ||||
</tr> | </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> | <tr> | ||||
<td>ownerPassword</td> | <td>ownerPassword</td> | ||||
<td>The owner password</td> | <td>The owner password</td> | ||||
</tr> | </tr> | ||||
<tr> | <tr> | ||||
<td>allowEditContent</td> | <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" or "FALSE"</td> | ||||
<td>"TRUE"</td> | <td>"TRUE"</td> | ||||
</tr> | </tr> | ||||
<td>"TRUE" or "FALSE"</td> | <td>"TRUE" or "FALSE"</td> | ||||
<td>"TRUE"</td> | <td>"TRUE"</td> | ||||
</tr> | </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> | </table> | ||||
<note> | <note> | ||||
Encryption is enabled as soon as one of these options is set. | Encryption is enabled as soon as one of these options is set. | ||||
<li>allowCopyContent: true if copying content is allowed</li> | <li>allowCopyContent: true if copying content is allowed</li> | ||||
<li>allowEditContent: true if editing content is allowed</li> | <li>allowEditContent: true if editing content is allowed</li> | ||||
<li>allowEditAnnotations: true if editing annotations 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> | </ol> | ||||
<p> | <p> | ||||
Alternatively, you can set each value separately in the Map provided by | Alternatively, you can set each value separately in the Map provided by | ||||
<li>nocopy: Boolean or "true"/"false"</li> | <li>nocopy: Boolean or "true"/"false"</li> | ||||
<li>noedit: Boolean or "true"/"false"</li> | <li>noedit: Boolean or "true"/"false"</li> | ||||
<li>noannotations: 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> | </ol> | ||||
</section> | </section> | ||||
<section> | <section> |
-nocopy PDF file will be encrypted without copy content permission | -nocopy PDF file will be encrypted without copy content permission | ||||
-noedit PDF file will be encrypted without edit content permission | -noedit PDF file will be encrypted without edit content permission | ||||
-noannotations PDF file will be encrypted without edit annotation 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) | -a enables accessibility features (Tagged PDF etc., default off) | ||||
-pdfprofile prof PDF file will be generated with the specified profile | -pdfprofile prof PDF file will be generated with the specified profile | ||||
(Examples for prof: PDF/A-1b or PDF/X-3:2003) | (Examples for prof: PDF/A-1b or PDF/X-3:2003) |
getPDFEncryptionParams().setAllowEditContent(false); | getPDFEncryptionParams().setAllowEditContent(false); | ||||
} else if (args[i].equals("-noannotations")) { | } else if (args[i].equals("-noannotations")) { | ||||
getPDFEncryptionParams().setAllowEditAnnotations(false); | 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")) { | } else if (args[i].equals("-version")) { | ||||
printVersion(); | printVersion(); | ||||
return false; | return false; | ||||
+ " -nocopy PDF file will be encrypted without copy content permission\n" | + " -nocopy PDF file will be encrypted without copy content permission\n" | ||||
+ " -noedit PDF file will be encrypted without edit 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" | + " -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" | + " -a enables accessibility features (Tagged PDF etc., default off)\n" | ||||
+ " -pdfprofile prof PDF file will be generated with the specified profile\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" | + " (Examples for prof: PDF/A-1b or PDF/X-3:2003)\n\n" |
/* | |||||
* 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); | |||||
} | |||||
} |
import java.io.OutputStream; | import java.io.OutputStream; | ||||
import java.io.UnsupportedEncodingException; | import java.io.UnsupportedEncodingException; | ||||
import java.io.Writer; | import java.io.Writer; | ||||
import java.security.MessageDigest; | |||||
import java.security.NoSuchAlgorithmException; | import java.security.NoSuchAlgorithmException; | ||||
import java.text.DateFormat; | |||||
import java.text.SimpleDateFormat; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Collections; | import java.util.Collections; | ||||
import java.util.Date; | import java.util.Date; | ||||
private boolean encodingOnTheFly = true; | private boolean encodingOnTheFly = true; | ||||
private FileIDGenerator fileIDGenerator; | |||||
/** | /** | ||||
* Creates an empty PDF document. | * Creates an empty PDF document. | ||||
* | * | ||||
*/ | */ | ||||
public void setEncryption(PDFEncryptionParams params) { | public void setEncryption(PDFEncryptionParams params) { | ||||
getProfile().verifyEncryptionAllowed(); | getProfile().verifyEncryptionAllowed(); | ||||
this.encryption = PDFEncryptionManager.newInstance(++this.objectcount, params); | |||||
fileIDGenerator = FileIDGenerator.getRandomFileIDGenerator(); | |||||
this.encryption = PDFEncryptionManager.newInstance(++this.objectcount, params, this); | |||||
if (this.encryption != null) { | if (this.encryption != null) { | ||||
PDFObject pdfObject = (PDFObject)this.encryption; | PDFObject pdfObject = (PDFObject)this.encryption; | ||||
pdfObject.setDocument(this); | |||||
addTrailerObject(pdfObject); | addTrailerObject(pdfObject); | ||||
} else { | } else { | ||||
log.warn( | log.warn( | ||||
this.position += bin.length; | 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 | * Write the trailer | ||||
* | * | ||||
if (this.isEncryptionActive()) { | if (this.isEncryptionActive()) { | ||||
pdf.append(this.encryption.getTrailerEntry()); | pdf.append(this.encryption.getTrailerEntry()); | ||||
} else { | } else { | ||||
pdf.append(this.getIDEntry()); | |||||
byte[] fileID = getFileIDGenerator().getOriginalFileID(); | |||||
String fileIDAsString = PDFText.toHex(fileID); | |||||
pdf.append("/ID [" + fileIDAsString + " " + fileIDAsString + "]"); | |||||
} | } | ||||
pdf.append("\n>>\nstartxref\n") | pdf.append("\n>>\nstartxref\n") | ||||
return pdfBytes.length; | 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; | |||||
} | |||||
} | } |
*/ | */ | ||||
public interface PDFEncryption { | 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 | * Adds a PDFFilter to the PDFStream object | ||||
* @param stream the stream to add an encryption filter to | * @param stream the stream to add an encryption filter to |
package org.apache.fop.pdf; | package org.apache.fop.pdf; | ||||
// Java | |||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.OutputStream; | import java.io.OutputStream; | ||||
import java.security.InvalidKeyException; | import java.security.InvalidKeyException; | ||||
import java.security.MessageDigest; | import java.security.MessageDigest; | ||||
import java.security.NoSuchAlgorithmException; | import java.security.NoSuchAlgorithmException; | ||||
import java.util.Random; | |||||
import java.util.Arrays; | |||||
import javax.crypto.BadPaddingException; | import javax.crypto.BadPaddingException; | ||||
import javax.crypto.Cipher; | import javax.crypto.Cipher; | ||||
import javax.crypto.spec.SecretKeySpec; | 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} */ | /** {@inheritDoc} */ | ||||
if (o == null) { | if (o == null) { | ||||
throw new IllegalStateException("No object number could be obtained for a PDF object"); | 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) { | public void applyFilter(AbstractPDFStream stream) { | ||||
stream.getFilterList().addFilter( | 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() { | 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; | |||||
} | |||||
} | } |
* Creates a new PDFEncryption instance if PDF encryption is available. | * Creates a new PDFEncryption instance if PDF encryption is available. | ||||
* @param objnum PDF object number | * @param objnum PDF object number | ||||
* @param params PDF encryption parameters | * @param params PDF encryption parameters | ||||
* @param pdf the PDF document to encrypt | |||||
* @return PDFEncryption the newly created instance, null if PDF encryption | * @return PDFEncryption the newly created instance, null if PDF encryption | ||||
* is unavailable. | * is unavailable. | ||||
*/ | */ | ||||
public static PDFEncryption newInstance(int objnum, PDFEncryptionParams params) { | |||||
public static PDFEncryption newInstance(int objnum, PDFEncryptionParams params, | |||||
PDFDocument pdf) { | |||||
try { | try { | ||||
Class clazz = Class.forName("org.apache.fop.pdf.PDFEncryptionJCE"); | |||||
Class<?> clazz = Class.forName("org.apache.fop.pdf.PDFEncryptionJCE"); | |||||
Method makeMethod = clazz.getMethod("make", | Method makeMethod = clazz.getMethod("make", | ||||
new Class[] {int.class, PDFEncryptionParams.class}); | |||||
new Class[] {int.class, PDFEncryptionParams.class, PDFDocument.class}); | |||||
Object obj = makeMethod.invoke(null, | Object obj = makeMethod.invoke(null, | ||||
new Object[] {new Integer(objnum), params}); | |||||
new Object[] {new Integer(objnum), params, pdf}); | |||||
return (PDFEncryption)obj; | return (PDFEncryption)obj; | ||||
} catch (ClassNotFoundException e) { | } catch (ClassNotFoundException e) { | ||||
if (checkAvailableAlgorithms()) { | if (checkAvailableAlgorithms()) { |
private String userPassword = ""; //May not be null | private String userPassword = ""; //May not be null | ||||
private String ownerPassword = ""; //May not be null | private String ownerPassword = ""; //May not be null | ||||
private boolean allowPrint = true; | private boolean allowPrint = true; | ||||
private boolean allowCopyContent = true; | private boolean allowCopyContent = true; | ||||
private boolean allowEditContent = true; | private boolean allowEditContent = true; | ||||
private boolean allowEditAnnotations = 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. | * Creates a new instance. | ||||
//nop | //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. | * Indicates whether copying content is allowed. | ||||
* @return true if copying is allowed | * @return true if copying is allowed | ||||
return allowPrint; | 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. | * Returns the owner password. | ||||
* @return the owner password, an empty string if no password applies | * @return the owner password, an empty string if no password applies | ||||
} | } | ||||
/** | /** | ||||
* Sets the persmission for printing. | |||||
* Sets the permission for printing. | |||||
* @param allowPrint true if printing is allowed | * @param allowPrint true if printing is allowed | ||||
*/ | */ | ||||
public void setAllowPrint(boolean allowPrint) { | public void setAllowPrint(boolean allowPrint) { | ||||
this.allowPrint = 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. | * Sets the owner password. | ||||
* @param ownerPassword The owner password to set, null or an empty String | * @param ownerPassword The owner password to set, null or an empty String | ||||
} | } | ||||
} | } | ||||
/** | |||||
* 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; | |||||
} | |||||
} | } |
} | } | ||||
/** | /** | ||||
* Returns the object's generation. | |||||
* Returns this object's generation. | |||||
* @return the PDF Object generation | * @return the PDF Object generation | ||||
*/ | */ | ||||
public int getGeneration() { | public int getGeneration() { |
String NO_EDIT_CONTENT = "noedit"; | String NO_EDIT_CONTENT = "noedit"; | ||||
/** PDF encryption parameter: Forbids annotations, datatype: Boolean or "true"/"false" */ | /** PDF encryption parameter: Forbids annotations, datatype: Boolean or "true"/"false" */ | ||||
String NO_ANNOTATIONS = "noannotations"; | 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. */ | /** Rendering Options key for the PDF/A mode. */ | ||||
String PDF_A_MODE = "pdf-a-mode"; | String PDF_A_MODE = "pdf-a-mode"; | ||||
/** Rendering Options key for the PDF/X mode. */ | /** Rendering Options key for the PDF/X mode. */ |
* @event.severity WARN | * @event.severity WARN | ||||
*/ | */ | ||||
void nonStandardStructureType(Object source, String fo, String type, String fallback); | 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); | |||||
} | } |
<catalogue xml:lang="en"> | <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="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="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> | </catalogue> |
Configuration encryptionParamsConfig | Configuration encryptionParamsConfig | ||||
= cfg.getChild(PDFConfigurationConstants.ENCRYPTION_PARAMS, false); | = cfg.getChild(PDFConfigurationConstants.ENCRYPTION_PARAMS, false); | ||||
if (encryptionParamsConfig != null) { | if (encryptionParamsConfig != null) { | ||||
PDFEncryptionParams encryptionParams = new PDFEncryptionParams(); | |||||
PDFEncryptionParams encryptionParams = pdfUtil.getEncryptionParams(); | |||||
Configuration ownerPasswordConfig = encryptionParamsConfig.getChild( | Configuration ownerPasswordConfig = encryptionParamsConfig.getChild( | ||||
PDFConfigurationConstants.OWNER_PASSWORD, false); | PDFConfigurationConstants.OWNER_PASSWORD, false); | ||||
if (ownerPasswordConfig != null) { | if (ownerPasswordConfig != null) { | ||||
if (noAnnotationsConfig != null) { | if (noAnnotationsConfig != null) { | ||||
encryptionParams.setAllowEditAnnotations(false); | 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); | s = cfg.getChild(PDFConfigurationConstants.KEY_OUTPUT_PROFILE, true).getValue(null); | ||||
if (s != null) { | if (s != null) { | ||||
pdfUtil.setOutputProfileURI(s); | pdfUtil.setOutputProfileURI(s); | ||||
} | } | ||||
} | } | ||||
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. | * Builds a filter map from an Avalon Configuration object. | ||||
* | * |
if (params != null) { | if (params != null) { | ||||
this.encryptionParams = params; //overwrite if available | 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); | String s = (String)userAgent.getRendererOptions().get(PDF_A_MODE); | ||||
if (s != null) { | if (s != null) { | ||||
if (s != null) { | if (s != null) { | ||||
this.outputProfileURI = s; | 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); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* 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() { | private void updateInfo() { |
documents. Example: the fix of marks layering will be such a case when it's done. | documents. Example: the fix of marks layering will be such a case when it's done. | ||||
--> | --> | ||||
<release version="FOP Trunk" date="TBD"> | <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"> | <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 | Fixed a bug in AFP where the object area axes of an Include Object was incorrectly set when | ||||
rotated by 180. </action> | rotated by 180. </action> |
import junit.framework.TestSuite; | import junit.framework.TestSuite; | ||||
import org.apache.fop.events.BasicEventTestCase; | 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.pdf.PDFObjectTestCase; | ||||
import org.apache.fop.traits.BorderPropsTestCase; | import org.apache.fop.traits.BorderPropsTestCase; | ||||
import org.apache.fop.util.BitmapImageUtilTestCase; | import org.apache.fop.util.BitmapImageUtilTestCase; | ||||
//$JUnit-BEGIN$ | //$JUnit-BEGIN$ | ||||
suite.addTest(new TestSuite(PDFNumberTestCase.class)); | suite.addTest(new TestSuite(PDFNumberTestCase.class)); | ||||
suite.addTest(new TestSuite(PDFObjectTestCase.class)); | suite.addTest(new TestSuite(PDFObjectTestCase.class)); | ||||
suite.addTest(FileIDGeneratorTestCase.suite()); | |||||
suite.addTest(new TestSuite(ColorUtilTestCase.class)); | suite.addTest(new TestSuite(ColorUtilTestCase.class)); | ||||
suite.addTest(new TestSuite(BorderPropsTestCase.class)); | suite.addTest(new TestSuite(BorderPropsTestCase.class)); | ||||
suite.addTest(new TestSuite(ElementListUtilsTestCase.class)); | suite.addTest(new TestSuite(ElementListUtilsTestCase.class)); | ||||
suite.addTest(new TestSuite(XMLResourceBundleTestCase.class)); | suite.addTest(new TestSuite(XMLResourceBundleTestCase.class)); | ||||
suite.addTest(new TestSuite(URIResolutionTestCase.class)); | suite.addTest(new TestSuite(URIResolutionTestCase.class)); | ||||
suite.addTest(new TestSuite(BitmapImageUtilTestCase.class)); | suite.addTest(new TestSuite(BitmapImageUtilTestCase.class)); | ||||
suite.addTest(new TestSuite(PDFEncryptionJCETestCase.class)); | |||||
//$JUnit-END$ | //$JUnit-END$ | ||||
return suite; | return suite; | ||||
} | } |
/* | |||||
* 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")); | |||||
} | |||||
} | |||||
} |
/* | |||||
* 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); | |||||
} | |||||
} |
/* | |||||
* 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()); | |||||
} | |||||
} |
/* | |||||
* 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; | |||||
} | |||||
} |
<?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> |
<?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> |
<?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> |
<?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> |
<?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> |