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/07/31 15:38:05 UTC
svn commit: r1880482 - in /pdfbox/trunk/examples: pom.xml
src/test/java/org/apache/pdfbox/examples/pdmodel/TestCreateSignature.java
Author: tilman
Date: Fri Jul 31 15:38:05 2020
New Revision: 1880482
URL: http://svn.apache.org/viewvc?rev=1880482&view=rev
Log:
PDFBOX-3017: add test for AddValidationInformation.java example (LTV) to increase code coverage
Modified:
pdfbox/trunk/examples/pom.xml
pdfbox/trunk/examples/src/test/java/org/apache/pdfbox/examples/pdmodel/TestCreateSignature.java
Modified: pdfbox/trunk/examples/pom.xml
URL: http://svn.apache.org/viewvc/pdfbox/trunk/examples/pom.xml?rev=1880482&r1=1880481&r2=1880482&view=diff
==============================================================================
--- pdfbox/trunk/examples/pom.xml (original)
+++ pdfbox/trunk/examples/pom.xml Fri Jul 31 15:38:05 2020
@@ -131,6 +131,25 @@
</excludes>
</configuration>
</plugin>
+ <plugin>
+ <groupId>com.googlecode.maven-download-plugin</groupId>
+ <artifactId>download-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>testAddValidationInformation</id>
+ <phase>generate-test-resources</phase>
+ <goals>
+ <goal>wget</goal>
+ </goals>
+ <configuration>
+ <url>https://www.quovadisglobal.com/wp-content/uploads/2020/01/QV_RCA1_RCA3_CPCPS_V4_11.pdf</url>
+ <outputDirectory>${project.build.directory}/pdfs</outputDirectory>
+ <outputFileName>QV_RCA1_RCA3_CPCPS_V4_11.pdf</outputFileName>
+ <sha512>940ab0cc5ad45c7b46fba4a079fd69803540bfd68f059326f62fe8d322fb0cc176cf303b9d1d679cb663edf279efe20c2452cc5eba69f5b8afadfca4c77cdb86</sha512>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
</plugins>
</build>
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=1880482&r1=1880481&r2=1880482&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 Fri Jul 31 15:38:05 2020
@@ -40,6 +40,7 @@ import java.security.UnrecoverableKeyExc
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
+import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.text.MessageFormat;
import java.util.Arrays;
@@ -48,9 +49,14 @@ import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import org.apache.pdfbox.Loader;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInputStream;
import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
import org.apache.pdfbox.cos.COSString;
import org.apache.pdfbox.examples.interactive.form.CreateSimpleForm;
import org.apache.pdfbox.examples.signature.CreateEmbeddedTimeStamp;
@@ -60,8 +66,10 @@ import org.apache.pdfbox.examples.signat
import org.apache.pdfbox.examples.signature.CreateVisibleSignature;
import org.apache.pdfbox.examples.signature.SigUtils;
import org.apache.pdfbox.examples.signature.cert.CertificateVerificationException;
+import org.apache.pdfbox.examples.signature.validation.AddValidationInformation;
import org.apache.pdfbox.io.IOUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.encryption.SecurityProvider;
@@ -72,15 +80,21 @@ import org.apache.pdfbox.pdmodel.interac
import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.pdfbox.util.Hex;
import org.apache.wink.client.MockHttpServer;
+import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.ocsp.BasicOCSPResp;
+import org.bouncycastle.cert.ocsp.OCSPException;
+import org.bouncycastle.cert.ocsp.OCSPResp;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.crypto.prng.FixedSecureRandom;
+import org.bouncycastle.operator.ContentVerifierProvider;
import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
import org.bouncycastle.tsp.TSPException;
import org.bouncycastle.tsp.TSPValidationException;
import org.bouncycastle.tsp.TimeStampToken;
@@ -645,8 +659,7 @@ public class TestCreateSignature
signingTime);
byte[] fixedRandomSignedTwo = signEncrypted(new FixedSecureRandom(new byte[128]),
signingTime);
- Assert.assertTrue(Arrays.equals(fixedRandomSignedOne, fixedRandomSignedTwo));
-
+ Assert.assertArrayEquals(fixedRandomSignedOne, fixedRandomSignedTwo);
}
/**
@@ -709,6 +722,192 @@ public class TestCreateSignature
SigUtils.verifyCertificateChain(certificatesStore, certFromSignedData, timeStampToken.getTimeStampInfo().getGenTime());
}
+ /**
+ * Test adding LTV information. This tests the status quo. If we use a new file (or if the file
+ * gets updated) then the test may have to be adjusted. The test is not really perfect, but it
+ * tries to check a minimum of things that should match. If the test fails and you didn't change
+ * anything in signing, then find out whether some external servers involved are unresponsive.
+ * At the time of writing this, the OCSP server http://ocsp.quovadisglobal.com responds with 502
+ * "UNAUTHORIZED". That is not a problem as long as the CRL URL works.
+ *
+ * @throws java.io.IOException
+ * @throws java.security.GeneralSecurityException
+ * @throws org.bouncycastle.cert.ocsp.OCSPException
+ * @throws org.bouncycastle.operator.OperatorCreationException
+ * @throws org.bouncycastle.cms.CMSException
+ */
+ @Test
+ public void testAddValidationInformation()
+ throws IOException, GeneralSecurityException, OCSPException, OperatorCreationException, CMSException
+ {
+ File inFile = new File("target/pdfs", "QV_RCA1_RCA3_CPCPS_V4_11.pdf");
+ String name = inFile.getName();
+ String substring = name.substring(0, name.lastIndexOf('.'));
+
+ File outFile = new File(OUT_DIR, substring + "_LTV.pdf");
+ AddValidationInformation addValidationInformation = new AddValidationInformation();
+ addValidationInformation.validateSignature(inFile, outFile);
+
+ CertificateFactory factory = CertificateFactory.getInstance("X.509");
+ PDDocument doc = Loader.loadPDF(outFile);
+
+ PDSignature signature = doc.getLastSignatureDictionary();
+ COSString contents = (COSString) signature.getCOSObject().getDictionaryObject(COSName.CONTENTS);
+
+ PDDocumentCatalog docCatalog = doc.getDocumentCatalog();
+ COSDictionary dssDict = docCatalog.getCOSObject().getCOSDictionary(COSName.getPDFName("DSS"));
+ COSArray dssCertArray = dssDict.getCOSArray(COSName.getPDFName("Certs"));
+ COSDictionary vriDict = dssDict.getCOSDictionary(COSName.getPDFName("VRI"));
+
+ // Check that all known signature certificates are in the VRI/signaturehash/Cert array
+ byte[] signatureHash = MessageDigest.getInstance("SHA-1").digest(contents.getBytes());
+ String hexSignatureHash = Hex.getString(signatureHash);
+ System.out.println("hexSignatureHash: " + hexSignatureHash);
+ CMSSignedData signedData = new CMSSignedData(contents.getBytes());
+ Store<X509CertificateHolder> certificatesStore = signedData.getCertificates();
+ HashSet<X509CertificateHolder> certificateHolderSet =
+ new HashSet<>(certificatesStore.getMatches(null));
+ COSDictionary sigDict = vriDict.getCOSDictionary(COSName.getPDFName(hexSignatureHash));
+ COSArray sigCertArray = sigDict.getCOSArray(COSName.getPDFName("Cert"));
+ Set<X509CertificateHolder> sigCertHolderSetFromVRIArray = new HashSet<>();
+ for (int i = 0; i < sigCertArray.size(); ++i)
+ {
+ COSStream certStream = (COSStream) sigCertArray.getObject(i);
+ COSInputStream is = certStream.createInputStream();
+ sigCertHolderSetFromVRIArray.add(new X509CertificateHolder(IOUtils.toByteArray(is)));
+ is.close();
+ }
+ for (X509CertificateHolder holder : certificateHolderSet)
+ {
+ if (holder.getSubject().toString().contains("QuoVadis OCSP Authority Signature"))
+ {
+ continue; // not relevant here
+ }
+ Assert.assertTrue("VRI/signaturehash/Cert array doesn't contain " + holder.getSubject(),
+ sigCertHolderSetFromVRIArray.contains(holder));
+ }
+
+ // Get all certificates. Each one should either be issued (= signed) by a certificate of the set
+ Set<X509Certificate> certSet = new HashSet<>();
+ for (int i = 0; i < dssCertArray.size(); ++i)
+ {
+ COSStream certStream = (COSStream) dssCertArray.getObject(i);
+ COSInputStream is = certStream.createInputStream();
+ X509Certificate cert = (X509Certificate) factory.generateCertificate(is);
+ is.close();
+ certSet.add(cert);
+ }
+ for (X509Certificate cert : certSet)
+ {
+ boolean verified = false;
+ for (X509Certificate cert2 : certSet)
+ {
+ try
+ {
+ cert.verify(cert2.getPublicKey(), SecurityProvider.getProvider());
+ verified = true;
+ }
+ catch (GeneralSecurityException ex)
+ {
+ // not the issuer
+ }
+ }
+ Assert.assertTrue("Certificate " + cert.getSubjectX500Principal() +
+ " not issued by any certificate in the Certs array", verified);
+ }
+
+ // Each CRL should be signed by one of the certificates in Certs
+ Set<X509CRL> crlSet = new HashSet<>();
+ COSArray crlArray = dssDict.getCOSArray(COSName.getPDFName("CRLs"));
+ for (int i = 0; i < crlArray.size(); ++i)
+ {
+ COSStream crlStream = (COSStream) crlArray.getObject(i);
+ COSInputStream is = crlStream.createInputStream();
+ X509CRL cert = (X509CRL) factory.generateCRL(is);
+ is.close();
+ crlSet.add(cert);
+ }
+ for (X509CRL crl : crlSet)
+ {
+ boolean crlVerified = false;
+ X509Certificate crlIssuerCert = null;
+ for (X509Certificate cert : certSet)
+ {
+ try
+ {
+ crl.verify(cert.getPublicKey(), SecurityProvider.getProvider());
+ crlVerified = true;
+ crlIssuerCert = cert;
+ }
+ catch (GeneralSecurityException ex)
+ {
+ // not the issuer
+ }
+ }
+ Assert.assertTrue("issuer of CRL not found in Certs array", crlVerified);
+
+ byte[] crlSignatureHash = MessageDigest.getInstance("SHA-1").digest(crl.getSignature());
+ String hexCrlSignatureHash = Hex.getString(crlSignatureHash);
+ System.out.println("hexCrlSignatureHash: " + hexCrlSignatureHash);
+
+ // Check that the issueing Cert is in the VRI array
+ COSDictionary crlSigDict = vriDict.getCOSDictionary(COSName.getPDFName(hexCrlSignatureHash));
+ COSArray certArray2 = crlSigDict.getCOSArray(COSName.getPDFName("Cert"));
+ COSStream certStream = (COSStream) certArray2.getObject(0);
+ COSInputStream is2 = certStream.createInputStream();
+ X509CertificateHolder certHolder2 = new X509CertificateHolder(IOUtils.toByteArray(is2));
+ is2.close();
+
+ Assert.assertTrue("CRL issuer certificate missing in VRI " + hexCrlSignatureHash,
+ certHolder2.equals(new X509CertificateHolder(crlIssuerCert.getEncoded())));
+ }
+
+ Set<OCSPResp> oscpSet = new HashSet<>();
+ COSArray ocspArray = dssDict.getCOSArray(COSName.getPDFName("OCSPs"));
+ for (int i = 0; i < ocspArray.size(); ++i)
+ {
+ COSStream ocspStream = (COSStream) ocspArray.getObject(i);
+ COSInputStream is = ocspStream.createInputStream();
+ OCSPResp ocspResp = new OCSPResp(is);
+ is.close();
+ oscpSet.add(ocspResp);
+ }
+ for (OCSPResp ocspResp : oscpSet)
+ {
+ BasicOCSPResp basicResponse = (BasicOCSPResp) ocspResp.getResponseObject();
+ Assert.assertEquals(OCSPResponseStatus.SUCCESSFUL, ocspResp.getStatus());
+ Assert.assertTrue("OCSP should have at least 1 certificate", basicResponse.getCerts().length >= 1);
+ byte[] ocspSignatureHash = MessageDigest.getInstance("SHA-1").digest(basicResponse.getSignature());
+ String hexOcspSignatureHash = Hex.getString(ocspSignatureHash);
+ System.out.println("ocspSignatureHash: " + hexOcspSignatureHash);
+ long secondsOld = (System.currentTimeMillis() - basicResponse.getProducedAt().getTime()) / 1000;
+ Assert.assertTrue("OCSP answer is too old", secondsOld < 10);
+
+ X509CertificateHolder ocspCertHolder = basicResponse.getCerts()[0];
+ ContentVerifierProvider verifier = new JcaContentVerifierProviderBuilder().setProvider(SecurityProvider.getProvider()).build(ocspCertHolder);
+ Assert.assertTrue(basicResponse.isSignatureValid(verifier));
+
+ COSDictionary ocspSigDict = vriDict.getCOSDictionary(COSName.getPDFName(hexOcspSignatureHash));
+
+ // Check that the Cert is in the VRI array
+ COSArray certArray2 = ocspSigDict.getCOSArray(COSName.getPDFName("Cert"));
+ COSStream certStream = (COSStream) certArray2.getObject(0);
+ COSInputStream is2 = certStream.createInputStream();
+ X509CertificateHolder certHolder2 = new X509CertificateHolder(IOUtils.toByteArray(is2));
+ is2.close();
+
+ Assert.assertTrue(certHolder2.equals(ocspCertHolder));
+ }
+
+ // TODO - split testCreateSignature (one with parameter, one without)
+ // - possibly DRY on certificate factory
+ // - split into smaller methods?
+ // - try-with-resources
+ // - branches
+
+ doc.close();
+ }
+
private byte[] signEncrypted(SecureRandom secureRandom, Date signingTime) throws Exception
{
KeyStore keystore = KeyStore.getInstance("PKCS12");