Browse Source

FOP-3166: Add option to sign PDF

pull/79/head
Simon Steiner 2 months ago
parent
commit
08d676ebd1

+ 12
- 0
fop-core/pom.xml View File

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

+ 63
- 0
fop-core/src/main/java/org/apache/fop/pdf/CMSProcessableInputStream.java View File

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

+ 57
- 0
fop-core/src/main/java/org/apache/fop/pdf/PDFSignParams.java View File

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

+ 277
- 0
fop-core/src/main/java/org/apache/fop/pdf/PDFSignature.java View File

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

+ 38
- 1
fop-core/src/main/java/org/apache/fop/render/pdf/PDFDocumentHandler.java View File

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

+ 18
- 0
fop-core/src/main/java/org/apache/fop/render/pdf/PDFRendererConfig.java View File

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

+ 6
- 0
fop-core/src/main/java/org/apache/fop/render/pdf/PDFRendererOption.java View File

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

+ 5
- 0
fop-core/src/main/java/org/apache/fop/render/pdf/PDFRendererOptionsConfig.java View File

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

+ 5
- 0
fop-core/src/main/java/org/apache/fop/render/pdf/PDFRenderingUtil.java View File

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

+ 41
- 0
fop-core/src/main/java/org/apache/fop/render/pdf/PDFSignOption.java View File

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

+ 97
- 0
fop-core/src/test/java/org/apache/fop/pdf/PDFSigningTestCase.java View File

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

BIN
fop-core/src/test/resources/org/apache/fop/pdf/keystore.pkcs12 View File


Loading…
Cancel
Save