@@ -111,6 +111,18 @@ | |||
<version>1.1.3</version> | |||
<scope>provided</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.bouncycastle</groupId> | |||
<artifactId>bcpkix-jdk15to18</artifactId> | |||
<version>1.77</version> | |||
<scope>provided</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.bouncycastle</groupId> | |||
<artifactId>bcprov-jdk15to18</artifactId> | |||
<version>1.77</version> | |||
<scope>provided</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.sun.media</groupId> | |||
<artifactId>jai-codec</artifactId> |
@@ -0,0 +1,63 @@ | |||
/* | |||
* 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. | |||
*/ | |||
package org.apache.fop.pdf; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.OutputStream; | |||
import org.bouncycastle.asn1.ASN1ObjectIdentifier; | |||
import org.bouncycastle.asn1.cms.CMSObjectIdentifiers; | |||
import org.bouncycastle.cms.CMSTypedData; | |||
import org.apache.commons.io.IOUtils; | |||
/** | |||
* Wraps a InputStream into a CMSProcessable object for bouncy castle. It's a memory saving | |||
* alternative to the {@link org.bouncycastle.cms.CMSProcessableByteArray CMSProcessableByteArray} | |||
* class. | |||
*/ | |||
class CMSProcessableInputStream implements CMSTypedData { | |||
private InputStream in; | |||
private final ASN1ObjectIdentifier contentType; | |||
CMSProcessableInputStream(InputStream is) { | |||
this(new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId()), is); | |||
} | |||
CMSProcessableInputStream(ASN1ObjectIdentifier type, InputStream is) { | |||
contentType = type; | |||
in = is; | |||
} | |||
@Override | |||
public Object getContent() { | |||
return in; | |||
} | |||
@Override | |||
public void write(OutputStream out) throws IOException { | |||
// read the content only one time | |||
IOUtils.copy(in, out); | |||
in.close(); | |||
} | |||
@Override | |||
public ASN1ObjectIdentifier getContentType() { | |||
return contentType; | |||
} | |||
} |
@@ -0,0 +1,57 @@ | |||
/* | |||
* 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; | |||
public class PDFSignParams { | |||
private String pkcs12; | |||
private String name; | |||
private String location; | |||
private String reason; | |||
private String password = ""; | |||
public PDFSignParams(String pkcs12, String name, String location, String reason, String password) { | |||
this.pkcs12 = pkcs12; | |||
this.name = name; | |||
this.location = location; | |||
this.reason = reason; | |||
if (password != null) { | |||
this.password = password; | |||
} | |||
} | |||
public String getPkcs12() { | |||
return pkcs12; | |||
} | |||
public String getName() { | |||
return name; | |||
} | |||
public String getLocation() { | |||
return location; | |||
} | |||
public String getReason() { | |||
return reason; | |||
} | |||
public String getPassword() { | |||
return password; | |||
} | |||
} |
@@ -0,0 +1,277 @@ | |||
/* | |||
* 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.BufferedInputStream; | |||
import java.io.BufferedOutputStream; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.OutputStream; | |||
import java.net.URI; | |||
import java.net.URISyntaxException; | |||
import java.security.GeneralSecurityException; | |||
import java.security.KeyStore; | |||
import java.security.PrivateKey; | |||
import java.security.cert.Certificate; | |||
import java.security.cert.X509Certificate; | |||
import java.util.Arrays; | |||
import java.util.Date; | |||
import java.util.Enumeration; | |||
import org.bouncycastle.cert.jcajce.JcaCertStore; | |||
import org.bouncycastle.cms.CMSException; | |||
import org.bouncycastle.cms.CMSSignedData; | |||
import org.bouncycastle.cms.CMSSignedDataGenerator; | |||
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder; | |||
import org.bouncycastle.operator.ContentSigner; | |||
import org.bouncycastle.operator.OperatorException; | |||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; | |||
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; | |||
import org.apache.commons.io.IOUtils; | |||
import org.apache.commons.io.output.CountingOutputStream; | |||
import org.apache.xmlgraphics.io.TempResourceURIGenerator; | |||
import org.apache.fop.apps.FOUserAgent; | |||
public class PDFSignature { | |||
private static final int SIZE_OF_CONTENTS = 18944; | |||
private static final TempResourceURIGenerator TEMP_URI_GENERATOR = new TempResourceURIGenerator("pdfsign2"); | |||
private Perms perms; | |||
private PDFRoot root; | |||
private PrivateKey privateKey; | |||
private long startOfDocMDP; | |||
private long startOfContents; | |||
private FOUserAgent userAgent; | |||
private URI tempURI; | |||
private PDFSignParams signParams; | |||
static class TransformParams extends PDFDictionary { | |||
TransformParams() { | |||
put("Type", new PDFName("TransformParams")); | |||
put("P", 2); | |||
put("V", new PDFName("1.2")); | |||
} | |||
} | |||
static class SigRef extends PDFDictionary { | |||
SigRef() { | |||
put("Type", new PDFName("SigRef")); | |||
put("TransformMethod", new PDFName("DocMDP")); | |||
put("DigestMethod", new PDFName("SHA1")); | |||
put("TransformParams", new TransformParams()); | |||
} | |||
} | |||
class Contents extends PDFObject { | |||
protected String toPDFString() { | |||
return PDFText.toHex(new byte[SIZE_OF_CONTENTS / 2]); | |||
} | |||
public int output(OutputStream stream) throws IOException { | |||
CountingOutputStream countingOutputStream = (CountingOutputStream) stream; | |||
startOfContents = startOfDocMDP + countingOutputStream.getByteCount(); | |||
return super.output(stream); | |||
} | |||
} | |||
class DocMDP extends PDFDictionary { | |||
DocMDP() { | |||
put("Type", new PDFName("Sig")); | |||
put("Filter", new PDFName("Adobe.PPKLite")); | |||
put("SubFilter", new PDFName("adbe.pkcs7.detached")); | |||
if (signParams.getName() != null) { | |||
put("Name", signParams.getName()); | |||
} | |||
if (signParams.getLocation() != null) { | |||
put("Location", signParams.getLocation()); | |||
} | |||
if (signParams.getReason() != null) { | |||
put("Reason", signParams.getReason()); | |||
} | |||
put("M", PDFInfo.formatDateTime(new Date())); | |||
PDFArray array = new PDFArray(); | |||
array.add(new SigRef()); | |||
put("Reference", array); | |||
put("Contents", new Contents()); | |||
put("ByteRange", new PDFArray(0, 1000000000, 1000000000, 1000000000)); | |||
} | |||
public int output(OutputStream stream) throws IOException { | |||
if (stream instanceof CountingOutputStream) { | |||
CountingOutputStream countingOutputStream = (CountingOutputStream) stream; | |||
startOfDocMDP = countingOutputStream.getByteCount(); | |||
return super.output(stream); | |||
} | |||
throw new IOException("Disable pdf linearization"); | |||
} | |||
} | |||
static class Perms extends PDFDictionary { | |||
DocMDP docMDP; | |||
Perms(PDFRoot root, DocMDP docMDP) { | |||
this.docMDP = docMDP; | |||
root.getDocument().registerObject(docMDP); | |||
put("DocMDP", docMDP); | |||
} | |||
} | |||
static class SigField extends PDFDictionary { | |||
SigField(Perms perms, PDFPage page, PDFRoot root) { | |||
root.getDocument().registerObject(this); | |||
put("FT", new PDFName("Sig")); | |||
put("Type", new PDFName("Annot")); | |||
put("Subtype", new PDFName("Widget")); | |||
put("F", 132); | |||
put("T", "Signature1"); | |||
put("Rect", new PDFRectangle(0, 0, 0, 0)); | |||
put("V", perms.docMDP); | |||
put("P", new PDFReference(page)); | |||
put("AP", new AP(root)); | |||
} | |||
} | |||
static class AP extends PDFDictionary { | |||
AP(PDFRoot root) { | |||
put("N", new FormXObject(root)); | |||
} | |||
} | |||
static class FormXObject extends PDFStream { | |||
FormXObject(PDFRoot root) { | |||
root.getDocument().registerObject(this); | |||
put("Length", 0); | |||
put("Type", new PDFName("XObject")); | |||
put("Subtype", new PDFName("Form")); | |||
put("BBox", new PDFRectangle(0, 0, 0, 0)); | |||
} | |||
} | |||
static class AcroForm extends PDFDictionary { | |||
AcroForm(SigField sigField) { | |||
PDFArray fields = new PDFArray(); | |||
fields.add(sigField); | |||
put("Fields", fields); | |||
put("SigFlags", 3); | |||
} | |||
} | |||
public PDFSignature(PDFRoot root, FOUserAgent userAgent, PDFSignParams signParams) { | |||
this.root = root; | |||
this.userAgent = userAgent; | |||
this.signParams = signParams; | |||
perms = new Perms(root, new DocMDP()); | |||
root.put("Perms", perms); | |||
tempURI = TEMP_URI_GENERATOR.generate(); | |||
} | |||
public void add(PDFPage page) { | |||
SigField sigField = new SigField(perms, page, root); | |||
root.put("AcroForm", new AcroForm(sigField)); | |||
page.addAnnotation(sigField); | |||
} | |||
public void signPDF(URI uri, OutputStream os) throws IOException { | |||
try (InputStream pdfIS = getTempIS(uri)) { | |||
pdfIS.mark(Integer.MAX_VALUE); | |||
String byteRangeValues = "0 1000000000 1000000000 1000000000"; | |||
String byteRange = "\n /ByteRange [" + byteRangeValues + "]"; | |||
int pdfLength = pdfIS.available(); | |||
long offsetToPDFEnd = startOfContents + SIZE_OF_CONTENTS + 2 + byteRange.length(); | |||
long endOfPDFSize = pdfLength - offsetToPDFEnd; | |||
String byteRangeValues2 = String.format("0 %s %s %s", startOfContents, | |||
startOfContents + SIZE_OF_CONTENTS + 2, byteRange.length() + endOfPDFSize); | |||
byteRange = "\n /ByteRange [" + byteRangeValues2 + "]"; | |||
String byteRangePadding = new String(new char[byteRangeValues.length() - byteRangeValues2.length()]) | |||
.replace("\0", " "); | |||
try (OutputStream editedPDF = getTempOS()) { | |||
IOUtils.copyLarge(pdfIS, editedPDF, 0, startOfContents); | |||
editedPDF.write(byteRange.getBytes("UTF-8")); | |||
editedPDF.write(byteRangePadding.getBytes("UTF-8")); | |||
IOUtils.copyLarge(pdfIS, editedPDF, offsetToPDFEnd - startOfContents, Long.MAX_VALUE); | |||
} | |||
pdfIS.reset(); | |||
IOUtils.copyLarge(pdfIS, os, 0, startOfContents); | |||
try (InputStream is = getTempIS(tempURI)) { | |||
byte[] signed = readPKCS(is); | |||
String signedHexPadding = new String(new char[SIZE_OF_CONTENTS - (signed.length * 2)]) | |||
.replace("\0", "0"); | |||
String signedHex = "<" + PDFText.toHex(signed, false) + signedHexPadding + ">"; | |||
os.write(signedHex.getBytes("UTF-8")); | |||
} | |||
os.write(byteRange.getBytes("UTF-8")); | |||
os.write(byteRangePadding.getBytes("UTF-8")); | |||
IOUtils.copyLarge(pdfIS, os, offsetToPDFEnd - startOfContents, Long.MAX_VALUE); | |||
} | |||
} | |||
private OutputStream getTempOS() throws IOException { | |||
return new BufferedOutputStream(userAgent.getResourceResolver().getOutputStream(tempURI)); | |||
} | |||
private InputStream getTempIS(URI uri) throws IOException { | |||
return new BufferedInputStream(userAgent.getResourceResolver().getResource(uri)); | |||
} | |||
private byte[] readPKCS(InputStream pdf) throws IOException { | |||
try { | |||
char[] password = signParams.getPassword().toCharArray(); | |||
KeyStore keystore = KeyStore.getInstance("PKCS12"); | |||
try (InputStream is = userAgent.getResourceResolver().getResource(signParams.getPkcs12())) { | |||
keystore.load(is, password); | |||
} | |||
Certificate[] certificates = readKeystore(keystore, password); | |||
return sign(pdf, certificates); | |||
} catch (GeneralSecurityException | URISyntaxException | OperatorException | CMSException e) { | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
private Certificate[] readKeystore(KeyStore keystore, char[] password) | |||
throws GeneralSecurityException, IOException { | |||
Enumeration<String> aliases = keystore.aliases(); | |||
while (aliases.hasMoreElements()) { | |||
String alias = aliases.nextElement(); | |||
privateKey = (PrivateKey) keystore.getKey(alias, password); | |||
Certificate[] certChain = keystore.getCertificateChain(alias); | |||
if (certChain != null) { | |||
Certificate cert = certChain[0]; | |||
if (cert instanceof X509Certificate) { | |||
((X509Certificate) cert).checkValidity(); | |||
} | |||
return certChain; | |||
} | |||
} | |||
throw new IOException("Could not find certificate"); | |||
} | |||
private byte[] sign(InputStream content, Certificate[] certChain) | |||
throws GeneralSecurityException, OperatorException, CMSException, IOException { | |||
CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); | |||
X509Certificate cert = (X509Certificate) certChain[0]; | |||
ContentSigner sha2Signer = new JcaContentSignerBuilder("SHA256WithRSA").build(privateKey); | |||
gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder( | |||
new JcaDigestCalculatorProviderBuilder().build()).build(sha2Signer, cert)); | |||
gen.addCertificates(new JcaCertStore(Arrays.asList(certChain))); | |||
CMSProcessableInputStream msg = new CMSProcessableInputStream(content); | |||
CMSSignedData signedData = gen.generate(msg, false); | |||
return signedData.getEncoded(); | |||
} | |||
} |
@@ -24,14 +24,19 @@ import java.awt.Rectangle; | |||
import java.awt.geom.AffineTransform; | |||
import java.awt.geom.Point2D; | |||
import java.awt.geom.Rectangle2D; | |||
import java.io.BufferedOutputStream; | |||
import java.io.IOException; | |||
import java.io.OutputStream; | |||
import java.net.URI; | |||
import java.util.HashMap; | |||
import java.util.Locale; | |||
import java.util.Map; | |||
import org.apache.commons.io.output.CountingOutputStream; | |||
import org.apache.commons.logging.Log; | |||
import org.apache.commons.logging.LogFactory; | |||
import org.apache.xmlgraphics.io.TempResourceURIGenerator; | |||
import org.apache.xmlgraphics.xmp.Metadata; | |||
import org.apache.fop.accessibility.StructureTreeEventHandler; | |||
@@ -43,6 +48,7 @@ import org.apache.fop.pdf.PDFDocument; | |||
import org.apache.fop.pdf.PDFPage; | |||
import org.apache.fop.pdf.PDFReference; | |||
import org.apache.fop.pdf.PDFResources; | |||
import org.apache.fop.pdf.PDFSignature; | |||
import org.apache.fop.pdf.PDFStream; | |||
import org.apache.fop.render.extensions.prepress.PageBoundaries; | |||
import org.apache.fop.render.extensions.prepress.PageScale; | |||
@@ -63,6 +69,7 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { | |||
/** logging instance */ | |||
private static Log log = LogFactory.getLog(PDFDocumentHandler.class); | |||
private static final TempResourceURIGenerator SIGN_TEMP_URI_GENERATOR = new TempResourceURIGenerator("pdfsign"); | |||
private boolean accessEnabled; | |||
@@ -100,6 +107,9 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { | |||
private Map<String, Object> usedFieldNames = new HashMap<>(); | |||
private Map<Integer, PDFArray> pageNumbers = new HashMap<Integer, PDFArray>(); | |||
private Map<String, PDFReference> contents = new HashMap<String, PDFReference>(); | |||
private PDFSignature pdfSignature; | |||
private URI signTempURI; | |||
private OutputStream orgOutputStream; | |||
/** | |||
* Default constructor. | |||
@@ -157,7 +167,8 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { | |||
public void startDocument() throws IFException { | |||
super.startDocument(); | |||
try { | |||
this.pdfDoc = pdfUtil.setupPDFDocument(this.outputStream); | |||
setupPDFSigning(); | |||
this.pdfDoc = pdfUtil.setupPDFDocument(outputStream); | |||
this.accessEnabled = getUserAgent().isAccessibilityEnabled(); | |||
if (accessEnabled) { | |||
setupAccessibility(); | |||
@@ -167,6 +178,15 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { | |||
} | |||
} | |||
private void setupPDFSigning() throws IOException { | |||
if (pdfUtil.getSignParams() != null) { | |||
orgOutputStream = outputStream; | |||
signTempURI = SIGN_TEMP_URI_GENERATOR.generate(); | |||
outputStream = new BufferedOutputStream(getUserAgent().getResourceResolver().getOutputStream(signTempURI)); | |||
outputStream = new CountingOutputStream(outputStream); | |||
} | |||
} | |||
private void setupAccessibility() { | |||
pdfDoc.getRoot().makeTagged(); | |||
logicalStructureHandler = new PDFLogicalStructureHandler(pdfDoc); | |||
@@ -201,6 +221,18 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { | |||
throw new IFException("I/O error in endDocument()", ioe); | |||
} | |||
super.endDocument(); | |||
signPDF(); | |||
} | |||
private void signPDF() { | |||
if (signTempURI != null) { | |||
try { | |||
outputStream.close(); | |||
pdfSignature.signPDF(signTempURI, orgOutputStream); | |||
} catch (IOException e) { | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
} | |||
/** {@inheritDoc} */ | |||
@@ -266,6 +298,11 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { | |||
basicPageTransform.scale(scaleX, scaleY); | |||
generator.saveGraphicsState(); | |||
generator.concatenate(basicPageTransform); | |||
if (signTempURI != null && pdfSignature == null) { | |||
pdfSignature = new PDFSignature(pdfDoc.getRoot(), getUserAgent(), pdfUtil.getSignParams()); | |||
pdfSignature.add(currentPage); | |||
} | |||
} | |||
private Rectangle2D toPDFCoordSystem(Rectangle box, AffineTransform transform) { |
@@ -38,6 +38,7 @@ import org.apache.fop.fonts.DefaultFontConfig.DefaultFontConfigParser; | |||
import org.apache.fop.fonts.FontEventAdapter; | |||
import org.apache.fop.pdf.PDFEncryptionParams; | |||
import org.apache.fop.pdf.PDFFilterList; | |||
import org.apache.fop.pdf.PDFSignParams; | |||
import org.apache.fop.render.RendererConfig; | |||
import org.apache.fop.render.RendererConfigOption; | |||
import org.apache.fop.util.LogUtil; | |||
@@ -155,11 +156,28 @@ public final class PDFRendererConfig implements RendererConfig { | |||
parseAndPut(LINEARIZATION, cfg); | |||
parseAndPut(FORM_XOBJECT, cfg); | |||
parseAndPut(VERSION, cfg); | |||
configureSignParams(cfg); | |||
} catch (ConfigurationException e) { | |||
LogUtil.handleException(LOG, e, strict); | |||
} | |||
} | |||
private void configureSignParams(Configuration cfg) throws FOPException { | |||
Configuration signCfd = cfg.getChild(PDFSignOption.SIGN_PARAMS, false); | |||
if (signCfd != null) { | |||
String keystore = parseConfig(signCfd, PDFSignOption.KEYSTORE); | |||
if (keystore == null) { | |||
throw new FOPException("No keystore file defined inside sign-params"); | |||
} | |||
String name = parseConfig(signCfd, PDFSignOption.NAME); | |||
String location = parseConfig(signCfd, PDFSignOption.LOCATION); | |||
String reason = parseConfig(signCfd, PDFSignOption.REASON); | |||
String password = parseConfig(signCfd, PDFSignOption.PASSWORD); | |||
PDFSignParams signParams = new PDFSignParams(keystore, name, location, reason, password); | |||
configOptions.put(PDFRendererOption.SIGN_PARAMS, signParams); | |||
} | |||
} | |||
private void configureEncryptionParams(Configuration cfg, FOUserAgent userAgent, boolean strict) { | |||
Configuration encryptCfg = cfg.getChild(ENCRYPTION_PARAMS, false); | |||
if (encryptCfg != null) { |
@@ -115,6 +115,12 @@ public enum PDFRendererOption implements RendererConfigOption { | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
}, | |||
SIGN_PARAMS(PDFSignOption.SIGN_PARAMS, null) { | |||
@Override | |||
Object deserialize(String value) { | |||
throw new UnsupportedOperationException(); | |||
} | |||
}; | |||
private final String name; |
@@ -26,6 +26,7 @@ import java.util.Map; | |||
import org.apache.fop.pdf.PDFAMode; | |||
import org.apache.fop.pdf.PDFEncryptionParams; | |||
import org.apache.fop.pdf.PDFSignParams; | |||
import org.apache.fop.pdf.PDFUAMode; | |||
import org.apache.fop.pdf.PDFVTMode; | |||
import org.apache.fop.pdf.PDFXMode; | |||
@@ -125,6 +126,10 @@ public final class PDFRendererOptionsConfig { | |||
return encryptionConfig; | |||
} | |||
public PDFSignParams getSignParams() { | |||
return (PDFSignParams) properties.get(PDFRendererOption.SIGN_PARAMS); | |||
} | |||
public URI getOutputProfileURI() { | |||
return (URI) properties.get(OUTPUT_PROFILE); | |||
} |
@@ -74,6 +74,7 @@ import org.apache.fop.pdf.PDFPage; | |||
import org.apache.fop.pdf.PDFPageLabels; | |||
import org.apache.fop.pdf.PDFReference; | |||
import org.apache.fop.pdf.PDFSetOCGStateAction; | |||
import org.apache.fop.pdf.PDFSignParams; | |||
import org.apache.fop.pdf.PDFTransitionAction; | |||
import org.apache.fop.pdf.PDFXMode; | |||
import org.apache.fop.pdf.Version; | |||
@@ -638,6 +639,10 @@ class PDFRenderingUtil { | |||
return this.pdfDoc; | |||
} | |||
public PDFSignParams getSignParams() { | |||
return rendererConfig.getSignParams(); | |||
} | |||
/** | |||
* Generates a page label in the PDF document. | |||
* @param pageIndex the index of the page |
@@ -0,0 +1,41 @@ | |||
/* | |||
* 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 org.apache.fop.render.RendererConfigOption; | |||
public enum PDFSignOption implements RendererConfigOption { | |||
KEYSTORE("keystore"), | |||
NAME("name"), | |||
LOCATION("location"), | |||
REASON("reason"), | |||
PASSWORD("password"); | |||
public static final String SIGN_PARAMS = "sign-params"; | |||
private final String name; | |||
PDFSignOption(String name) { | |||
this.name = name; | |||
} | |||
public String getName() { | |||
return name; | |||
} | |||
public Object getDefaultValue() { | |||
return null; | |||
} | |||
} |
@@ -0,0 +1,97 @@ | |||
/* | |||
* 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.ByteArrayInputStream; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.File; | |||
import java.util.StringTokenizer; | |||
import javax.xml.transform.Result; | |||
import javax.xml.transform.Source; | |||
import javax.xml.transform.Transformer; | |||
import javax.xml.transform.TransformerFactory; | |||
import javax.xml.transform.sax.SAXResult; | |||
import javax.xml.transform.stream.StreamSource; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
import org.apache.fop.apps.FOUserAgent; | |||
import org.apache.fop.apps.Fop; | |||
import org.apache.fop.apps.FopFactory; | |||
import org.apache.fop.apps.MimeConstants; | |||
import org.apache.fop.fo.pagination.LayoutMasterSetTestCase; | |||
public class PDFSigningTestCase { | |||
@Test | |||
public void textFO() throws Exception { | |||
ByteArrayOutputStream out = new ByteArrayOutputStream(); | |||
foToOutput(out, MimeConstants.MIME_PDF); | |||
StringTokenizer byteRange = new StringTokenizer(out.toString().split("/ByteRange ")[1]); | |||
byteRange.nextToken(); | |||
int startOfContents = Integer.parseInt(byteRange.nextToken()); | |||
int endOfContents = Integer.parseInt(byteRange.nextToken()); | |||
int sizeOfEnd = Integer.parseInt(byteRange.nextToken().replace("]", "")); | |||
int sizeOfContents = 18944; | |||
Assert.assertEquals(endOfContents, startOfContents + sizeOfContents + 2); | |||
Assert.assertEquals(out.size(), startOfContents + sizeOfContents + 2 + sizeOfEnd); | |||
ByteArrayInputStream bis = new ByteArrayInputStream(out.toByteArray()); | |||
bis.skip(startOfContents); | |||
Assert.assertEquals(bis.read(), '<'); | |||
bis.skip(sizeOfContents); | |||
Assert.assertEquals(bis.read(), '>'); | |||
byte[] end = new byte[200]; | |||
bis.read(end); | |||
String endStr = new String(end); | |||
Assert.assertTrue(endStr.contains( | |||
"/ByteRange [0 " + startOfContents + " " + endOfContents + " " + sizeOfEnd + "]")); | |||
Assert.assertTrue(endStr.contains("/FT /Sig\n" | |||
+ " /Type /Annot\n" | |||
+ " /Subtype /Widget\n" | |||
+ " /F 132\n" | |||
+ " /T (Signature1)\n" | |||
+ " /Rect [0 0 0 0]")); | |||
} | |||
private void foToOutput(ByteArrayOutputStream out, String mimeFopIf) throws Exception { | |||
FopFactory fopFactory = getFopFactory(); | |||
FOUserAgent userAgent = fopFactory.newFOUserAgent(); | |||
Fop fop = fopFactory.newFop(mimeFopIf, userAgent, out); | |||
Transformer transformer = TransformerFactory.newInstance().newTransformer(); | |||
Source src = new StreamSource(LayoutMasterSetTestCase.class.getResourceAsStream("side-regions.fo")); | |||
Result res = new SAXResult(fop.getDefaultHandler()); | |||
transformer.transform(src, res); | |||
} | |||
private FopFactory getFopFactory() throws Exception { | |||
String pkcs = PDFSigningTestCase.class.getResource("keystore.pkcs12").toString(); | |||
String fopxconf = "<fop version=\"1.0\">\n" | |||
+ " <renderers>\n" | |||
+ " <renderer mime=\"application/pdf\">\n" | |||
+ " <sign-params>\n" | |||
+ " <keystore>" + pkcs + "</keystore>\n" | |||
+ " </sign-params>\n" | |||
+ " </renderer>\n" | |||
+ " </renderers>\n" | |||
+ "</fop>\n"; | |||
return FopFactory.newInstance(new File(".").toURI(), | |||
new ByteArrayInputStream(fopxconf.getBytes())); | |||
} | |||
} |