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 2020/04/22 19:48:11 UTC

svn commit: r1876851 - /pdfbox/trunk/examples/src/test/java/org/apache/pdfbox/examples/pdmodel/TestCreateSignature.java

Author: tilman
Date: Wed Apr 22 19:48:10 2020
New Revision: 1876851

URL: http://svn.apache.org/viewvc?rev=1876851&view=rev
Log:
PDFBOX-3017: add methods to verify timestamp signature

Modified:
    pdfbox/trunk/examples/src/test/java/org/apache/pdfbox/examples/pdmodel/TestCreateSignature.java

Modified: pdfbox/trunk/examples/src/test/java/org/apache/pdfbox/examples/pdmodel/TestCreateSignature.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/examples/src/test/java/org/apache/pdfbox/examples/pdmodel/TestCreateSignature.java?rev=1876851&r1=1876850&r2=1876851&view=diff
==============================================================================
--- pdfbox/trunk/examples/src/test/java/org/apache/pdfbox/examples/pdmodel/TestCreateSignature.java (original)
+++ pdfbox/trunk/examples/src/test/java/org/apache/pdfbox/examples/pdmodel/TestCreateSignature.java Wed Apr 22 19:48:10 2020
@@ -51,20 +51,28 @@ import org.apache.pdfbox.io.IOUtils;
 import org.apache.pdfbox.pdmodel.PDDocument;
 import org.apache.pdfbox.pdmodel.PDPage;
 import org.apache.pdfbox.pdmodel.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.encryption.SecurityProvider;
 import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
 import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
 import org.apache.pdfbox.pdmodel.interactive.form.PDField;
 import org.apache.pdfbox.rendering.PDFRenderer;
 import org.apache.pdfbox.util.Hex;
 import org.apache.wink.client.MockHttpServer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.cert.X509CertificateHolder;
 import org.bouncycastle.cms.CMSException;
 import org.bouncycastle.cms.CMSProcessableByteArray;
 import org.bouncycastle.cms.CMSSignedData;
 import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.SignerInformationVerifier;
 import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
 import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.tsp.TSPException;
 import org.bouncycastle.tsp.TSPValidationException;
+import org.bouncycastle.tsp.TimeStampToken;
 import org.bouncycastle.util.Selector;
 import org.bouncycastle.util.Store;
 import org.junit.Assert;
@@ -118,10 +126,12 @@ public class TestCreateSignature
      * @throws GeneralSecurityException
      * @throws CMSException
      * @throws OperatorCreationException
+     * @throws TSPException
      */
     @Test
     public void testDetachedSHA256()
-            throws IOException, CMSException, OperatorCreationException, GeneralSecurityException
+            throws IOException, CMSException, OperatorCreationException, GeneralSecurityException,
+                   TSPException
     {
         // load the keystore
         KeyStore keystore = KeyStore.getInstance("PKCS12");
@@ -150,6 +160,7 @@ public class TestCreateSignature
      * @throws GeneralSecurityException
      * @throws CMSException
      * @throws OperatorCreationException
+     * @throws TSPException
      */
     @Test
     public void testDetachedSHA256WithTSA()
@@ -197,10 +208,12 @@ public class TestCreateSignature
      * @throws CMSException
      * @throws OperatorCreationException
      * @throws GeneralSecurityException
+     * @throws TSPException
      */
     @Test
     public void testCreateVisibleSignature()
-            throws IOException, CMSException, OperatorCreationException, GeneralSecurityException
+            throws IOException, CMSException, OperatorCreationException, GeneralSecurityException,
+                   TSPException
     {
         // load the keystore
         KeyStore keystore = KeyStore.getInstance("PKCS12");
@@ -233,12 +246,14 @@ public class TestCreateSignature
      * @throws UnrecoverableKeyException
      * @throws CMSException
      * @throws OperatorCreationException
-     * @throws GeneralSecurityException 
+     * @throws GeneralSecurityException
+     * @throws TSPException
      */
     @Test
     public void testPDFBox3978() throws IOException, NoSuchAlgorithmException, KeyStoreException, 
                                         CertificateException, UnrecoverableKeyException, 
-                                        CMSException, OperatorCreationException, GeneralSecurityException
+                                        CMSException, OperatorCreationException, GeneralSecurityException,
+                                        TSPException
     {
         String filename        = OUT_DIR + "EmptySignatureForm.pdf";
         String filenameSigned1 = OUT_DIR + "EmptySignatureForm-signed1.pdf";
@@ -295,7 +310,8 @@ public class TestCreateSignature
 
     // This check fails with a file created with the code before PDFBOX-3011 was solved.
     private void checkSignature(File origFile, File signedFile)
-            throws IOException, CMSException, OperatorCreationException, GeneralSecurityException
+            throws IOException, CMSException, OperatorCreationException, GeneralSecurityException,
+            TSPException
     {
         String origPageKey;
         try (PDDocument document = Loader.loadPDF(origFile))
@@ -350,10 +366,47 @@ public class TestCreateSignature
                 {
                     Assert.fail("Signature verification failed");
                 }
+
+                TimeStampToken timeStampToken = extractTimeStampTokenFromSignerInformation(signerInformation);
+                if (timeStampToken != null)
+                {
+                    validateTimestampToken(timeStampToken);
+                }
             }
         }
     }
 
+    private void validateTimestampToken(TimeStampToken timeStampToken)
+            throws TSPException, CertificateException, OperatorCreationException, IOException
+    {
+        // https://stackoverflow.com/questions/42114742/
+        @SuppressWarnings("unchecked") // TimeStampToken.getSID() is untyped
+        Collection<X509CertificateHolder> tstMatches =
+                timeStampToken.getCertificates().getMatches((Selector<X509CertificateHolder>) timeStampToken.getSID());
+        X509CertificateHolder holder = tstMatches.iterator().next();
+        SignerInformationVerifier siv = new JcaSimpleSignerInfoVerifierBuilder().setProvider(SecurityProvider.getProvider()).build(holder);
+        timeStampToken.validate(siv);
+    }
+
+    private TimeStampToken extractTimeStampTokenFromSignerInformation(SignerInformation signerInformation)
+            throws CMSException, IOException, TSPException
+    {
+        if (signerInformation.getUnsignedAttributes() == null)
+        {
+            return null;
+        }
+        AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
+        // https://stackoverflow.com/questions/1647759/how-to-validate-if-a-signed-jar-contains-a-timestamp
+        Attribute attribute = unsignedAttributes.get(PKCSObjectIdentifiers.id_aa_signatureTimeStampToken);
+        if (attribute == null)
+        {
+            return null;
+        }
+        ASN1Object obj = (ASN1Object) attribute.getAttrValues().getObjectAt(0);
+        CMSSignedData signedTSTData = new CMSSignedData(obj.getEncoded());
+        return new TimeStampToken(signedTSTData);
+    }
+
     private String calculateDigestString(InputStream inputStream) throws NoSuchAlgorithmException, IOException
     {
         MessageDigest md = MessageDigest.getInstance("SHA-256");