You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by ti...@apache.org on 2018/03/06 21:00:28 UTC
svn commit: r1826043 - in /pdfbox/branches/2.0/examples/src:
main/java/org/apache/pdfbox/examples/signature/
test/java/org/apache/pdfbox/examples/pdmodel/
Author: tilman
Date: Tue Mar 6 21:00:28 2018
New Revision: 1826043
URL: http://svn.apache.org/viewvc?rev=1826043&view=rev
Log:
PDFBOX-4020: embed TimeStamp into existing signature + show DSS + refactor, by Alexis Suter
Added:
pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateEmbeddedTimeStamp.java (with props)
pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/ValidationTimeStamp.java (with props)
Modified:
pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateSignature.java
pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateSignatureBase.java
pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateSignedTimeStamp.java
pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateVisibleSignature.java
pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateVisibleSignature2.java
pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/ShowSignature.java
pdfbox/branches/2.0/examples/src/test/java/org/apache/pdfbox/examples/pdmodel/TestCreateSignature.java
Added: pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateEmbeddedTimeStamp.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateEmbeddedTimeStamp.java?rev=1826043&view=auto
==============================================================================
--- pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateEmbeddedTimeStamp.java (added)
+++ pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateEmbeddedTimeStamp.java Tue Mar 6 21:00:28 2018
@@ -0,0 +1,280 @@
+/*
+ * 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.pdfbox.examples.signature;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.io.IOUtils;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
+import org.apache.pdfbox.util.Hex;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.tsp.TSPException;
+
+/**
+ * An example for timestamp-singing a PDF for PADeS-Specification. The document will only be changed
+ * in its existing signature by a signed timestamp (A timestamp and the Hash-Value of the document
+ * are signed by a Time Stamp Authority (TSA)).
+ *
+ * This method only changes the unsigned parameters of a signature, so that it is kept valid.
+ *
+ * Use case: sign offline to avoid zero-day attacks against the signing machine. Once the signature
+ * is there and the pdf is transferred to a network connected machine, one is likely to want to add
+ * a timestamp. (Ralf Hauser)
+ *
+ * @author Alexis Suter
+ */
+public class CreateEmbeddedTimeStamp
+{
+ private final String tsaUrl;
+ private PDDocument document;
+ private PDSignature signature;
+ private byte[] changedEncodedSignature;
+
+ public CreateEmbeddedTimeStamp(String tsaUrl)
+ {
+ this.tsaUrl = tsaUrl;
+ }
+
+ /**
+ * Embeds the given PDF file with signed timestamp(s). Alters the original file on disk.
+ *
+ * @param file the PDF file to sign and to overwrite
+ * @throws IOException
+ */
+ public void embedTimeStamp(File file) throws IOException
+ {
+ embedTimeStamp(file, file);
+ }
+
+ /**
+ * Embeds signed timestamp(s) into existing signatures of the given document
+ *
+ * @param inFile The pdf file possibly containing signatures
+ * @param outFile Where the changed document will be saved
+ * @throws IOException
+ */
+ public void embedTimeStamp(File inFile, File outFile) throws IOException
+ {
+ if (inFile == null || !inFile.exists())
+ {
+ throw new FileNotFoundException("Document for signing does not exist");
+ }
+
+ // sign
+ PDDocument doc = PDDocument.load(inFile);
+ document = doc;
+ processTimeStamping(outFile, inFile.getAbsolutePath());
+ doc.close();
+ }
+
+ /**
+ * Processes the timestamping of the Signature.
+ *
+ * @param output Where the new file will be written to
+ * @param fileName of the existing file containing the pdf
+ * @throws IOException
+ */
+ private void processTimeStamping(File outFile, String fileName) throws IOException
+ {
+ int accessPermissions = SigUtils.getMDPPermission(document);
+ if (accessPermissions == 1)
+ {
+ throw new IllegalStateException(
+ "No changes to the document are permitted due to DocMDP transform parameters dictionary");
+ }
+
+ try
+ {
+ byte[] documentBytes;
+ FileInputStream fis = new FileInputStream(fileName);
+ documentBytes = IOUtils.toByteArray(fis);
+ fis.close();
+ processRelevantSignatures(documentBytes);
+
+ if (changedEncodedSignature != null)
+ {
+ FileOutputStream output = new FileOutputStream(outFile);
+ embedNewSignatureIntoDocument(documentBytes, output);
+ output.close();
+ }
+ }
+ catch (IOException e)
+ {
+ throw new IOException(e);
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ throw new IOException(e);
+ }
+ catch (CMSException e)
+ {
+ throw new IOException(e);
+ }
+ catch (TSPException e)
+ {
+ throw new IOException(e);
+ }
+ }
+
+ /**
+ * Create changed Signature with embedded TimeStamp from TSA
+ *
+ * @param documentInput Stream of the input file
+ * @throws IOException
+ * @throws CMSException
+ * @throws NoSuchAlgorithmException
+ * @throws TSPException
+ */
+ private void processRelevantSignatures(byte[] documentBytes)
+ throws IOException, CMSException, NoSuchAlgorithmException, TSPException
+ {
+ getRelevantSignature(document);
+ if (signature != null)
+ {
+ byte[] sigBlock = signature.getContents(documentBytes);
+ CMSSignedData signedData = new CMSSignedData(sigBlock);
+
+ // System.out.println("INFO: Old Signature: " + Hex.getString(sigBlock));
+ System.out.println("INFO: Byte Range: " + Arrays.toString(signature.getByteRange()));
+
+ if (tsaUrl != null && tsaUrl.length() > 0)
+ {
+ ValidationTimeStamp validation = new ValidationTimeStamp(tsaUrl);
+ signedData = validation.addSignedTimeStamp(signedData);
+ }
+
+ byte[] newEncoded = Hex.getBytes(signedData.getEncoded());
+ int maxSize = signature.getByteRange()[2] - signature.getByteRange()[1];
+ System.out.println(
+ "INFO: New Signature has Size: " + newEncoded.length + " maxSize: " + maxSize);
+
+ if (newEncoded.length > maxSize - 2)
+ {
+ throw new IOException(
+ "New Signature is too big for existing Signature-Placeholder. Max Place: "
+ + maxSize);
+ }
+ else
+ {
+ changedEncodedSignature = newEncoded;
+ }
+ }
+ }
+
+ /**
+ * Extracts last Document-Signature from the document
+ *
+ * @param document to get the Signature from
+ * @return the Signature, where a TimeStamp will be added. <code>null</code> when no Document-Signature available.
+ * @throws IOException
+ */
+ private void getRelevantSignature(PDDocument document) throws IOException
+ {
+ SortedMap<Integer, PDSignature> sortedMap = new TreeMap<Integer, PDSignature>();
+ for (PDSignature sig : document.getSignatureDictionaries())
+ {
+ int sigOffset = sig.getByteRange()[1];
+ sortedMap.put(sigOffset, sig);
+ }
+ if (sortedMap.size() > 0)
+ {
+ PDSignature lastSignature = sortedMap.get(sortedMap.lastKey());
+ COSBase type = lastSignature.getCOSObject().getItem(COSName.TYPE);
+ if (type.equals(COSName.SIG))
+ {
+ signature = lastSignature;
+ }
+ }
+ }
+
+ /**
+ * Embeds the new signature into the document, by copying the rest of the document
+ *
+ * @param docBytes byte array of the document
+ * @param output target, where the file will be written
+ * @throws IOException
+ */
+ private void embedNewSignatureIntoDocument(byte[] docBytes, OutputStream output)
+ throws IOException
+ {
+ int[] byteRange = signature.getByteRange();
+ output.write(docBytes, byteRange[0], byteRange[1] + 1);
+ output.write(changedEncodedSignature);
+ int addingLength = byteRange[2] - byteRange[1] - 2 - changedEncodedSignature.length;
+ byte[] zeroes = Hex.getBytes(new byte[(addingLength + 1) / 2]);
+ output.write(zeroes);
+ output.write(docBytes, byteRange[2] - 1, byteRange[3] + 1);
+ }
+
+ public static void main(String[] args) throws IOException, GeneralSecurityException
+ {
+ if (args.length != 3)
+ {
+ usage();
+ System.exit(1);
+ }
+
+ String tsaUrl = null;
+ for (int i = 0; i < args.length; i++)
+ {
+ if (args[i].equals("-tsa"))
+ {
+ i++;
+ if (i >= args.length)
+ {
+ usage();
+ System.exit(1);
+ }
+ tsaUrl = args[i];
+ }
+ }
+
+ File inFile = new File(args[0]);
+ System.out.println("Input File: " + args[0]);
+ String name = inFile.getName();
+ String substring = name.substring(0, name.lastIndexOf('.'));
+
+ File outFile = new File(inFile.getParent(), substring + "_eTs.pdf");
+ System.out.println("Output File: " + outFile.getAbsolutePath());
+
+ // Embed TimeStamp
+ CreateEmbeddedTimeStamp signing = new CreateEmbeddedTimeStamp(tsaUrl);
+ signing.embedTimeStamp(inFile, outFile);
+ }
+
+ private static void usage()
+ {
+ System.err.println("usage: java " + CreateEmbeddedTimeStamp.class.getName() + " "
+ + "<pdf_to_sign>\n" + "mandatory option:\n"
+ + " -tsa <url> sign timestamp using the given TSA server\n");
+ }
+}
Propchange: pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateEmbeddedTimeStamp.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateSignature.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateSignature.java?rev=1826043&r1=1826042&r2=1826043&view=diff
==============================================================================
--- pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateSignature.java (original)
+++ pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateSignature.java Tue Mar 6 21:00:28 2018
@@ -22,11 +22,9 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
-import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
-import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
@@ -35,6 +33,7 @@ import java.util.Calendar;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.ExternalSigningSupport;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions;
/**
* An example for singing a PDF with bouncy castle.
@@ -92,29 +91,29 @@ public class CreateSignature extends Cre
* Signs the given PDF file.
* @param inFile input PDF file
* @param outFile output PDF file
- * @param tsaClient optional TSA client
+ * @param tsaUrl optional TSA url
* @throws IOException if the input file could not be read
*/
- public void signDetached(File inFile, File outFile, TSAClient tsaClient) throws IOException
+ public void signDetached(File inFile, File outFile, String tsaUrl) throws IOException
{
if (inFile == null || !inFile.exists())
{
throw new FileNotFoundException("Document for signing does not exist");
}
+ setTsaUrl(tsaUrl);
+
FileOutputStream fos = new FileOutputStream(outFile);
// sign
PDDocument doc = PDDocument.load(inFile);
- signDetached(doc, fos, tsaClient);
+ signDetached(doc, fos);
doc.close();
}
- public void signDetached(PDDocument document, OutputStream output, TSAClient tsaClient)
+ public void signDetached(PDDocument document, OutputStream output)
throws IOException
{
- setTsaClient(tsaClient);
-
int accessPermissions = SigUtils.getMDPPermission(document);
if (accessPermissions == 1)
{
@@ -152,8 +151,11 @@ public class CreateSignature extends Cre
}
else
{
+ SignatureOptions signatureOptions = new SignatureOptions();
+ // Size can vary, but should be enough for purpose.
+ signatureOptions.setPreferredSignatureSize(SignatureOptions.DEFAULT_SIGNATURE_SIZE * 2);
// register signature dictionary and sign interface
- document.addSignature(signature, this);
+ document.addSignature(signature, this, signatureOptions);
// write incremental (only for signing purpose)
document.saveIncremental(output);
@@ -194,14 +196,6 @@ public class CreateSignature extends Cre
keystore.load(new FileInputStream(args[0]), password);
// TODO alias command line argument
- // TSA client
- TSAClient tsaClient = null;
- if (tsaUrl != null)
- {
- MessageDigest digest = MessageDigest.getInstance("SHA-256");
- tsaClient = new TSAClient(new URL(tsaUrl), null, null, digest);
- }
-
// sign PDF
CreateSignature signing = new CreateSignature(keystore, password);
signing.setExternalSigning(externalSig);
@@ -211,7 +205,7 @@ public class CreateSignature extends Cre
String substring = name.substring(0, name.lastIndexOf('.'));
File outFile = new File(inFile.getParent(), substring + "_signed.pdf");
- signing.signDetached(inFile, outFile, tsaClient);
+ signing.signDetached(inFile, outFile, tsaUrl);
}
private static void usage()
Modified: pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateSignatureBase.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateSignatureBase.java?rev=1826043&r1=1826042&r2=1826043&view=diff
==============================================================================
--- pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateSignatureBase.java (original)
+++ pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateSignatureBase.java Tue Mar 6 21:00:28 2018
@@ -32,22 +32,11 @@ import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1ObjectIdentifier;
-import org.bouncycastle.asn1.ASN1Primitive;
-import org.bouncycastle.asn1.DERSet;
-import org.bouncycastle.asn1.cms.Attribute;
-import org.bouncycastle.asn1.cms.AttributeTable;
-import org.bouncycastle.asn1.cms.Attributes;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.cert.X509CertificateHolder;
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.SignerInformation;
-import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
@@ -59,7 +48,7 @@ public abstract class CreateSignatureBas
{
private PrivateKey privateKey;
private Certificate[] certificateChain;
- private TSAClient tsaClient;
+ private String tsaUrl;
private boolean externalSigning;
/**
@@ -118,71 +107,14 @@ public abstract class CreateSignatureBas
this.certificateChain = certificateChain;
}
- public void setTsaClient(TSAClient tsaClient)
+ public String getTsaUrl()
{
- this.tsaClient = tsaClient;
+ return tsaUrl;
}
- public TSAClient getTsaClient()
+ public void setTsaUrl(String tsaUrl)
{
- return tsaClient;
- }
-
- /**
- * We just extend CMS signed Data
- *
- * @param signedData Generated CMS signed data
- * @return CMSSignedData Extended CMS signed data
- * @throws IOException
- */
- private CMSSignedData signTimeStamps(CMSSignedData signedData)
- throws IOException
- {
- SignerInformationStore signerStore = signedData.getSignerInfos();
- List<SignerInformation> newSigners = new ArrayList<SignerInformation>();
-
- for (SignerInformation signer : signerStore.getSigners())
- {
- newSigners.add(signTimeStamp(signer));
- }
-
- // TODO do we have to return a new store?
- return CMSSignedData.replaceSigners(signedData, new SignerInformationStore(newSigners));
- }
-
- /**
- * We are extending CMS Signature
- *
- * @param signer information about signer
- * @return information about SignerInformation
- */
- private SignerInformation signTimeStamp(SignerInformation signer) throws IOException
- {
- AttributeTable unsignedAttributes = signer.getUnsignedAttributes();
-
- ASN1EncodableVector vector = new ASN1EncodableVector();
- if (unsignedAttributes != null)
- {
- vector = unsignedAttributes.toASN1EncodableVector();
- }
-
- byte[] token = getTsaClient().getTimeStampToken(signer.getSignature());
- ASN1ObjectIdentifier oid = PKCSObjectIdentifiers.id_aa_signatureTimeStampToken;
- ASN1Encodable signatureTimeStamp = new Attribute(oid, new DERSet(ASN1Primitive.fromByteArray(token)));
-
- vector.add(signatureTimeStamp);
- Attributes signedAttributes = new Attributes(vector);
-
- SignerInformation newSigner = SignerInformation.replaceUnsignedAttributes(
- signer, new AttributeTable(signedAttributes));
-
- // TODO can this actually happen?
- if (newSigner == null)
- {
- return signer;
- }
-
- return newSigner;
+ this.tsaUrl = tsaUrl;
}
/**
@@ -200,7 +132,7 @@ public abstract class CreateSignatureBas
@Override
public byte[] sign(InputStream content) throws IOException
{
- //TODO this method should be private
+ // cannot be done private (interface)
try
{
List<Certificate> certList = new ArrayList<Certificate>();
@@ -213,9 +145,10 @@ public abstract class CreateSignatureBas
gen.addCertificates(certs);
CMSProcessableInputStream msg = new CMSProcessableInputStream(content);
CMSSignedData signedData = gen.generate(msg, false);
- if (tsaClient != null)
+ if (tsaUrl != null && tsaUrl.length() > 0)
{
- signedData = signTimeStamps(signedData);
+ ValidationTimeStamp validation = new ValidationTimeStamp(tsaUrl);
+ signedData = validation.addSignedTimeStamp(signedData);
}
return signedData.getEncoded();
}
Modified: pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateSignedTimeStamp.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateSignedTimeStamp.java?rev=1826043&r1=1826042&r2=1826043&view=diff
==============================================================================
--- pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateSignedTimeStamp.java (original)
+++ pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateSignedTimeStamp.java Tue Mar 6 21:00:28 2018
@@ -20,32 +20,42 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
-import java.net.URL;
import java.security.GeneralSecurityException;
-import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
/**
- * An example for timestamp-singing a PDF for PADeS-Specification. The document will only be
- * extended by a signed Timestamp (Signed Timestamp and Hash-Value of the document are signed by a
- * Time Stamp Authority (TSA)).
+ * An example for timestamp-singing a PDF for PADeS-Specification. The document will be extended by
+ * a signed TimeStamp (another kind of signature) (Signed TimeStamp and Hash-Value of the document
+ * are signed by a Time Stamp Authority (TSA)).
*
* @author Thomas Chojecki
* @author Vakhtang Koroghlishvili
* @author John Hewson
+ * @author Alexis Suter
*/
-public class CreateSignedTimeStamp extends CreateSignedTimestampBase
+public class CreateSignedTimeStamp implements SignatureInterface
{
+ private static final Log LOG = LogFactory.getLog(CreateSignedTimeStamp.class);
+
+ private final String tsaUrl;
/**
* Initialize the signed timestamp creator
+ *
+ * @param tsaUrl The url where TS-Request will be done.
*/
- public CreateSignedTimeStamp()
+ public CreateSignedTimeStamp(String tsaUrl)
{
+ this.tsaUrl = tsaUrl;
}
/**
@@ -56,7 +66,7 @@ public class CreateSignedTimeStamp exten
*/
public void signDetached(File file) throws IOException
{
- signDetached(file, file, null);
+ signDetached(file, file);
}
/**
@@ -68,19 +78,6 @@ public class CreateSignedTimeStamp exten
*/
public void signDetached(File inFile, File outFile) throws IOException
{
- signDetached(inFile, outFile, null);
- }
-
- /**
- * Signs the given PDF file.
- *
- * @param inFile input PDF file
- * @param outFile output PDF file
- * @param tsaClient optional TSA client
- * @throws IOException if the input file could not be read
- */
- public void signDetached(File inFile, File outFile, TSAClient tsaClient) throws IOException
- {
if (inFile == null || !inFile.exists())
{
throw new FileNotFoundException("Document for signing does not exist");
@@ -90,15 +87,19 @@ public class CreateSignedTimeStamp exten
// sign
PDDocument doc = PDDocument.load(inFile);
- signDetached(doc, fos, tsaClient);
+ signDetached(doc, fos);
doc.close();
}
- public void signDetached(PDDocument document, OutputStream output, TSAClient tsaClient)
- throws IOException
+ /**
+ * Prepares the TimeStamp-Signature and starts the saving-process.
+ *
+ * @param document given Pdf
+ * @param output Where the file will be written
+ * @throws IOException
+ */
+ public void signDetached(PDDocument document, OutputStream output) throws IOException
{
- setTsaClient(tsaClient);
-
int accessPermissions = SigUtils.getMDPPermission(document);
if (accessPermissions == 1)
{
@@ -124,6 +125,22 @@ public class CreateSignedTimeStamp exten
document.saveIncremental(output);
}
+ @Override
+ public byte[] sign(InputStream content) throws IOException
+ {
+ ValidationTimeStamp validation;
+ try
+ {
+ validation = new ValidationTimeStamp(tsaUrl);
+ return validation.getTimeStampToken(content);
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ LOG.error("Hashing-Algorithm not found for TimeStamping", e);
+ }
+ return new byte[] {};
+ }
+
public static void main(String[] args) throws IOException, GeneralSecurityException
{
if (args.length != 3)
@@ -143,29 +160,21 @@ public class CreateSignedTimeStamp exten
System.exit(1);
}
- // TSA client
- TSAClient tsaClient = null;
- if (tsaUrl != null)
- {
- MessageDigest digest = MessageDigest.getInstance("SHA-256");
- tsaClient = new TSAClient(new URL(tsaUrl), null, null, digest);
- }
-
// sign PDF
- CreateSignedTimeStamp signing = new CreateSignedTimeStamp();
+ CreateSignedTimeStamp signing = new CreateSignedTimeStamp(tsaUrl);
File inFile = new File(args[0]);
String name = inFile.getName();
String substring = name.substring(0, name.lastIndexOf('.'));
File outFile = new File(inFile.getParent(), substring + "_timestamped.pdf");
- signing.signDetached(inFile, outFile, tsaClient);
+ signing.signDetached(inFile, outFile);
}
private static void usage()
{
System.err.println("usage: java " + CreateSignedTimeStamp.class.getName() + " "
- + "<pdf_to_sign>\n" + "options:\n"
+ + "<pdf_to_sign>\n" + "mandatory options:\n"
+ " -tsa <url> sign timestamp using the given TSA server\n");
}
}
Modified: pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateVisibleSignature.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateVisibleSignature.java?rev=1826043&r1=1826042&r2=1826043&view=diff
==============================================================================
--- pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateVisibleSignature.java (original)
+++ pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateVisibleSignature.java Tue Mar 6 21:00:28 2018
@@ -162,12 +162,12 @@ public class CreateVisibleSignature exte
*
* @param inputFile The source pdf document file.
* @param signedFile The file to be signed.
- * @param tsaClient optional TSA client
+ * @param tsaUrl optional TSA url
* @throws IOException
*/
- public void signPDF(File inputFile, File signedFile, TSAClient tsaClient) throws IOException
+ public void signPDF(File inputFile, File signedFile, String tsaUrl) throws IOException
{
- this.signPDF(inputFile, signedFile, tsaClient, null);
+ this.signPDF(inputFile, signedFile, tsaUrl, null);
}
/**
@@ -175,19 +175,19 @@ public class CreateVisibleSignature exte
*
* @param inputFile The source pdf document file.
* @param signedFile The file to be signed.
- * @param tsaClient optional TSA client
+ * @param tsaUrl optional TSA url
* @param signatureFieldName optional name of an existing (unsigned) signature field
* @throws IOException
*/
- public void signPDF(File inputFile, File signedFile, TSAClient tsaClient, String signatureFieldName) throws IOException
+ public void signPDF(File inputFile, File signedFile, String tsaUrl, String signatureFieldName) throws IOException
{
- setTsaClient(tsaClient);
-
if (inputFile == null || !inputFile.exists())
{
throw new IOException("Document for signing does not exist");
}
+ setTsaUrl(tsaUrl);
+
// creating output document and prepare the IO streams.
FileOutputStream fos = new FileOutputStream(signedFile);
@@ -411,14 +411,6 @@ public class CreateVisibleSignature exte
char[] pin = args[1].toCharArray();
keystore.load(new FileInputStream(ksFile), pin);
- // TSA client
- TSAClient tsaClient = null;
- if (tsaUrl != null)
- {
- MessageDigest digest = MessageDigest.getInstance("SHA-256");
- tsaClient = new TSAClient(new URL(tsaUrl), null, null, digest);
- }
-
File documentFile = new File(args[2]);
CreateVisibleSignature signing = new CreateVisibleSignature(keystore, pin.clone());
@@ -435,7 +427,7 @@ public class CreateVisibleSignature exte
imageStream.close();
signing.setVisibleSignatureProperties("name", "location", "Security", 0, page, true);
signing.setExternalSigning(externalSig);
- signing.signPDF(documentFile, signedDocumentFile, tsaClient);
+ signing.signPDF(documentFile, signedDocumentFile, tsaUrl);
}
/**
Modified: pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateVisibleSignature2.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateVisibleSignature2.java?rev=1826043&r1=1826042&r2=1826043&view=diff
==============================================================================
--- pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateVisibleSignature2.java (original)
+++ pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateVisibleSignature2.java Tue Mar 6 21:00:28 2018
@@ -130,12 +130,12 @@ public class CreateVisibleSignature2 ext
* @param inputFile The source pdf document file.
* @param signedFile The file to be signed.
* @param humanRect rectangle from a human viewpoint (coordinates start at top left)
- * @param tsaClient optional TSA client
+ * @param tsaUrl optional TSA url
* @throws IOException
*/
- public void signPDF(File inputFile, File signedFile, Rectangle2D humanRect, TSAClient tsaClient) throws IOException
+ public void signPDF(File inputFile, File signedFile, Rectangle2D humanRect, String tsaUrl) throws IOException
{
- this.signPDF(inputFile, signedFile, humanRect, tsaClient, null);
+ this.signPDF(inputFile, signedFile, humanRect, tsaUrl, null);
}
/**
@@ -144,18 +144,18 @@ public class CreateVisibleSignature2 ext
* @param inputFile The source pdf document file.
* @param signedFile The file to be signed.
* @param humanRect rectangle from a human viewpoint (coordinates start at top left)
- * @param tsaClient optional TSA client
+ * @param tsaUrl optional TSA url
* @param signatureFieldName optional name of an existing (unsigned) signature field
* @throws IOException
*/
- public void signPDF(File inputFile, File signedFile, Rectangle2D humanRect, TSAClient tsaClient, String signatureFieldName) throws IOException
+ public void signPDF(File inputFile, File signedFile, Rectangle2D humanRect, String tsaUrl, String signatureFieldName) throws IOException
{
if (inputFile == null || !inputFile.exists())
{
throw new IOException("Document for signing does not exist");
}
- setTsaClient(tsaClient);
+ setTsaUrl(tsaUrl);
// creating output document and prepare the IO streams.
FileOutputStream fos = new FileOutputStream(signedFile);
@@ -521,14 +521,6 @@ public class CreateVisibleSignature2 ext
KeyStore keystore = KeyStore.getInstance("PKCS12");
char[] pin = args[1].toCharArray();
keystore.load(new FileInputStream(ksFile), pin);
-
- // TSA client
- TSAClient tsaClient = null;
- if (tsaUrl != null)
- {
- MessageDigest digest = MessageDigest.getInstance("SHA-256");
- tsaClient = new TSAClient(new URL(tsaUrl), null, null, digest);
- }
File documentFile = new File(args[2]);
@@ -550,7 +542,7 @@ public class CreateVisibleSignature2 ext
// regardless of page rotation.
Rectangle2D humanRect = new Rectangle2D.Float(100, 200, 150, 50);
- signing.signPDF(documentFile, signedDocumentFile, humanRect, tsaClient, "Signature1");
+ signing.signPDF(documentFile, signedDocumentFile, humanRect, tsaUrl, "Signature1");
}
/**
Modified: pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/ShowSignature.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/ShowSignature.java?rev=1826043&r1=1826042&r2=1826043&view=diff
==============================================================================
--- pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/ShowSignature.java (original)
+++ pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/ShowSignature.java Tue Mar 6 21:00:28 2018
@@ -32,12 +32,20 @@ import java.security.cert.CertificateFac
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Collection;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInputStream;
import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.io.IOUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
+import org.apache.pdfbox.util.Hex;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cms.CMSException;
@@ -218,6 +226,7 @@ public final class ShowSignature
throw new IOException("Missing subfilter for cert dictionary");
}
}
+ analyseDSS(document);
}
catch (CMSException ex)
{
@@ -234,6 +243,7 @@ public final class ShowSignature
document.close();
}
}
+ System.out.println("Analyzed: " + args[1]);
}
}
@@ -286,6 +296,65 @@ public final class ShowSignature
}
}
+ /**
+ * Analyzes the DSS-Dictionary (Document security Store) of the document. Which is used for
+ * signature validation. The DSS is defined in PAdES Part 4 - Long Term Validation.
+ *
+ * @param document
+ */
+ private void analyseDSS(PDDocument document) throws IOException
+ {
+ PDDocumentCatalog catalog = document.getDocumentCatalog();
+ COSBase dssElement = catalog.getCOSObject().getDictionaryObject("DSS");
+
+ if (dssElement instanceof COSDictionary)
+ {
+ COSDictionary dss = (COSDictionary) dssElement;
+ System.out.println("DSS Dictionary: " + dss);
+ COSBase certsElement = dss.getDictionaryObject("Certs");
+ if (certsElement instanceof COSArray)
+ {
+ printStreamsFromArray((COSArray) certsElement, "Cert");
+ }
+ COSBase ocspsElement = dss.getDictionaryObject("OCSPs");
+ if (ocspsElement instanceof COSArray)
+ {
+ printStreamsFromArray((COSArray) ocspsElement, "Ocsp");
+ }
+ COSBase crlElement = dss.getDictionaryObject("CRLs");
+ if (crlElement instanceof COSArray)
+ {
+ printStreamsFromArray((COSArray) crlElement, "CRL");
+ }
+ // TODO: go through VRIs (wich indirectly point to the DSS-Data)
+ }
+ }
+
+ /**
+ * Go through the elements of a COSArray containing each an COSStream to print in Hex.
+ *
+ * @param elements COSArray of elements containing a COS Stream
+ * @param description to append on Print
+ * @throws IOException
+ */
+ private void printStreamsFromArray(COSArray elements, String description) throws IOException
+ {
+ for (COSBase baseElem : elements)
+ {
+ COSObject streamObj = (COSObject) baseElem;
+ if (streamObj.getObject() instanceof COSStream)
+ {
+ COSStream cosStream = (COSStream) streamObj.getObject();
+
+ COSInputStream input = cosStream.createInputStream();
+ byte[] streamBytes = IOUtils.toByteArray(input);
+
+ System.out.println(description + " (" + elements.indexOf(streamObj) + "): "
+ + Hex.getString(streamBytes));
+ }
+ }
+ }
+
// https://svn.apache.org/repos/asf/cxf/tags/cxf-2.4.1/distribution/src/main/release/samples/sts_issue_operation/src/main/java/demo/sts/provider/cert/CertificateVerifier.java
/**
Added: pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/ValidationTimeStamp.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/ValidationTimeStamp.java?rev=1826043&view=auto
==============================================================================
--- pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/ValidationTimeStamp.java (added)
+++ pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/ValidationTimeStamp.java Tue Mar 6 21:00:28 2018
@@ -0,0 +1,135 @@
+/*
+ * 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.pdfbox.examples.signature;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.io.IOUtils;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.Attributes;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.SignerInformationStore;
+
+/**
+ * This class wraps the TSAClient and the work that has to be done with it. Like Adding Signed
+ * TimeStamps to a signature, or creating a CMS timestamp attribute (with a signed timestamp)
+ *
+ * @author Others
+ * @author Alexis Suter
+ */
+public class ValidationTimeStamp
+{
+ private TSAClient tsaClient;
+
+ /**
+ * @param tsaUrl The url where TS-Request will be done.
+ * @throws NoSuchAlgorithmException
+ * @throws MalformedURLException
+ */
+ public ValidationTimeStamp(String tsaUrl) throws NoSuchAlgorithmException, MalformedURLException
+ {
+ if (tsaUrl != null)
+ {
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ this.tsaClient = new TSAClient(new URL(tsaUrl), null, null, digest);
+ }
+ }
+
+ /**
+ * Creates a signed timestamp token by the given input stream.
+ *
+ * @param content InputStream of the content to sign
+ * @return the byte[] of the timestamp token
+ * @throws IOException
+ */
+ public byte[] getTimeStampToken(InputStream content) throws IOException
+ {
+ return tsaClient.getTimeStampToken(IOUtils.toByteArray(content));
+ }
+
+ /**
+ * Extend cms signed data with TimeStamp first or to all signers
+ *
+ * @param signedData Generated CMS signed data
+ * @return CMSSignedData Extended CMS signed data
+ * @throws IOException
+ */
+ public CMSSignedData addSignedTimeStamp(CMSSignedData signedData)
+ throws IOException
+ {
+ SignerInformationStore signerStore = signedData.getSignerInfos();
+ List<SignerInformation> newSigners = new ArrayList<SignerInformation>();
+
+ for (SignerInformation signer : signerStore.getSigners())
+ {
+ // This adds a timestamp to every signer (into his unsigned attributes) in the signature.
+ newSigners.add(signTimeStamp(signer));
+ }
+
+ // Because new SignerInformation is created, new SignerInfoStore has to be created
+ // and also be replaced in signedData. Which creates a new signedData object.
+ return CMSSignedData.replaceSigners(signedData, new SignerInformationStore(newSigners));
+ }
+
+ /**
+ * Extend CMS Signer Information with the TimeStampToken into the unsigned Attributes.
+ *
+ * @param signer information about signer
+ * @return information about SignerInformation
+ * @throws IOException
+ */
+ private SignerInformation signTimeStamp(SignerInformation signer)
+ throws IOException
+ {
+ AttributeTable unsignedAttributes = signer.getUnsignedAttributes();
+
+ ASN1EncodableVector vector = new ASN1EncodableVector();
+ if (unsignedAttributes != null)
+ {
+ vector = unsignedAttributes.toASN1EncodableVector();
+ }
+
+ byte[] token = tsaClient.getTimeStampToken(signer.getSignature());
+ ASN1ObjectIdentifier oid = PKCSObjectIdentifiers.id_aa_signatureTimeStampToken;
+ ASN1Encodable signatureTimeStamp = new Attribute(oid,
+ new DERSet(ASN1Primitive.fromByteArray(token)));
+
+ vector.add(signatureTimeStamp);
+ Attributes signedAttributes = new Attributes(vector);
+
+ // There is no other way changing the unsigned attributes of the signer information.
+ // result is never null, new SignerInformation always returned,
+ // see source code of replaceUnsignedAttributes
+ return SignerInformation.replaceUnsignedAttributes(signer, new AttributeTable(signedAttributes));
+ }
+}
Propchange: pdfbox/branches/2.0/examples/src/main/java/org/apache/pdfbox/examples/signature/ValidationTimeStamp.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: pdfbox/branches/2.0/examples/src/test/java/org/apache/pdfbox/examples/pdmodel/TestCreateSignature.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/examples/src/test/java/org/apache/pdfbox/examples/pdmodel/TestCreateSignature.java?rev=1826043&r1=1826042&r2=1826043&view=diff
==============================================================================
--- pdfbox/branches/2.0/examples/src/test/java/org/apache/pdfbox/examples/pdmodel/TestCreateSignature.java (original)
+++ pdfbox/branches/2.0/examples/src/test/java/org/apache/pdfbox/examples/pdmodel/TestCreateSignature.java Tue Mar 6 21:00:28 2018
@@ -21,7 +21,6 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
@@ -42,7 +41,6 @@ import org.apache.pdfbox.cos.COSString;
import org.apache.pdfbox.examples.signature.CreateEmptySignatureForm;
import org.apache.pdfbox.examples.signature.CreateSignature;
import org.apache.pdfbox.examples.signature.CreateVisibleSignature;
-import org.apache.pdfbox.examples.signature.TSAClient;
import org.apache.pdfbox.io.IOUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
@@ -161,10 +159,6 @@ public class TestCreateSignature
response.setMockResponseCode(200);
mockServer.setMockHttpServerResponses(response);
- // TSA client
- MessageDigest digest = MessageDigest.getInstance("SHA-256");
- TSAClient tsaClient = new TSAClient(new URL(tsaUrl), null, null, digest);
-
// load the keystore
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(new FileInputStream(keystorePath), password.toCharArray());
@@ -176,7 +170,7 @@ public class TestCreateSignature
String outPath = outDir + getOutputFileName("signed{0}_tsa.pdf");
CreateSignature signing = new CreateSignature(keystore, password.toCharArray());
signing.setExternalSigning(externallySign);
- signing.signDetached(new File(inPath), new File(outPath), tsaClient);
+ signing.signDetached(new File(inPath), new File(outPath), tsaUrl);
}
catch (IOException e)
{