/** * The contents of this file are subject to the license and copyright * detailed in the LICENSE file at the root of the source * tree and available online at * * https://github.com/keeps/roda */ package org.roda.core.plugins.plugins.characterization; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.PrivateKey; import java.security.Security; import java.security.SignatureException; import java.security.cert.CRL; import java.security.cert.Certificate; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.itextpdf.text.DocumentException; import com.itextpdf.text.Rectangle; import com.itextpdf.text.pdf.AcroFields; import com.itextpdf.text.pdf.AcroFields.Item; import com.itextpdf.text.pdf.PdfDictionary; import com.itextpdf.text.pdf.PdfName; import com.itextpdf.text.pdf.PdfReader; import com.itextpdf.text.pdf.PdfSignatureAppearance; import com.itextpdf.text.pdf.PdfStamper; import com.itextpdf.text.pdf.PdfString; import com.itextpdf.text.pdf.security.BouncyCastleDigest; import com.itextpdf.text.pdf.security.DigestAlgorithms; import com.itextpdf.text.pdf.security.ExternalDigest; import com.itextpdf.text.pdf.security.ExternalSignature; import com.itextpdf.text.pdf.security.MakeSignature; import com.itextpdf.text.pdf.security.PdfPKCS7; import com.itextpdf.text.pdf.security.PrivateKeySignature; public final class PDFSignatureUtils { private static final Logger LOGGER = LoggerFactory.getLogger(PDFSignatureUtils.class); /** Private empty constructor */ private PDFSignatureUtils() { } public static String runDigitalSignatureVerify(Path input) throws IOException, GeneralSecurityException { Security.addProvider(new BouncyCastleProvider()); PdfReader reader = new PdfReader(input.toString()); AcroFields fields = reader.getAcroFields(); ArrayList<String> names = fields.getSignatureNames(); String result = "Passed"; for (int i = 0; i < names.size(); i++) { String name = names.get(i); try { PdfPKCS7 pk = fields.verifySignature(name); X509Certificate certificate = pk.getSigningCertificate(); certificate.checkValidity(); if (!SignatureUtils.isCertificateSelfSigned(certificate)) { Set<Certificate> trustedRootCerts = new HashSet<>(); Set<Certificate> intermediateCerts = new HashSet<>(); for (Certificate c : pk.getSignCertificateChain()) { X509Certificate cert = (X509Certificate) c; cert.checkValidity(); if (SignatureUtils.isCertificateSelfSigned(c)) trustedRootCerts.add(c); else intermediateCerts.add(c); } SignatureUtils.verifyCertificateChain(trustedRootCerts, intermediateCerts, certificate); if (pk.getCRLs() != null) { for (CRL crl : pk.getCRLs()) { if (crl.isRevoked(certificate)) { result = "Signing certificate is included on a Certificate Revocation List"; } } } } } catch (NoSuchFieldError e) { result = "Missing signature timestamp field"; } catch (CertificateExpiredException e) { result = "Contains expired certificates"; } catch (CertificateNotYetValidException e) { result = "Contains certificates not yet valid"; } } reader.close(); return result; } public static List<Path> runDigitalSignatureExtract(Path input) throws SignatureException, IOException { Security.addProvider(new BouncyCastleProvider()); List<Path> paths = new ArrayList<>(); Path output = Files.createTempFile("extraction", ".xml"); Path outputContents = Files.createTempFile("contents", ".pkcs7"); PdfReader reader = new PdfReader(input.toString()); AcroFields fields = reader.getAcroFields(); ArrayList<?> names = fields.getSignatureNames(); String filename = input.getFileName().toString(); filename = filename.substring(0, filename.lastIndexOf('.')); if (names.isEmpty()) return paths; StringBuilder sb = getExtractionInformation(fields, names, outputContents, filename); FileOutputStream fos = new FileOutputStream(output.toString()); OutputStreamWriter osw = new OutputStreamWriter(fos); try (PrintWriter out = new PrintWriter(osw, true)) { out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); out.println("<signatures>"); out.println(sb.toString()); out.println("</signatures>"); } reader.close(); paths.add(output); paths.add(outputContents); return paths; } public static void runDigitalSignatureStrip(Path input, Path output) throws IOException, DocumentException { PdfReader reader = new PdfReader(input.toString()); PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(output.toString())); stamper.setFormFlattening(true); stamper.close(); reader.close(); } public static Path runDigitalSignatureSign(Path input, String keystore, String alias, String password, String reason, String location, String contact) throws IOException, GeneralSecurityException, DocumentException { Security.addProvider(new BouncyCastleProvider()); Path signedPDF = Files.createTempFile("signed", ".pdf"); KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); try (InputStream is = new FileInputStream(keystore)) { ks.load(is, password.toCharArray()); PrivateKey pk = (PrivateKey) ks.getKey(alias, password.toCharArray()); Certificate[] chain = ks.getCertificateChain(alias); try (FileOutputStream os = new FileOutputStream(signedPDF.toFile())) { PdfReader reader = new PdfReader(input.toString()); PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0'); PdfSignatureAppearance appearance = stamper.getSignatureAppearance(); appearance.setReason(reason); appearance.setLocation(location); appearance.setContact(contact); appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "RODASignature"); ExternalDigest digest = new BouncyCastleDigest(); ExternalSignature signature = new PrivateKeySignature(pk, DigestAlgorithms.SHA256, "BC"); MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, null); reader.close(); } } return signedPDF; } private static StringBuilder getExtractionInformation(AcroFields fields, ArrayList<?> names, Path outputContents, String filename) throws IOException { StringBuilder sb = new StringBuilder(); for (int i = 0; i < names.size(); i++) { String name = (String) names.get(i); Item item = fields.getFieldItem(name); PdfDictionary widget = item.getWidget(0); PdfDictionary infoDictionary = widget.getAsDict(PdfName.V); sb.append("<signature>\n"); try { PdfPKCS7 pk = fields.verifySignature(name); sb = addElementToExtractionResult(sb, "name", name); sb = addElementToExtractionResult(sb, "sign-name", pk.getSignName()); sb = addElementToExtractionResult(sb, "version", Integer.toString(pk.getVersion())); sb = addElementToExtractionResult(sb, "reason", pk.getReason()); sb = addElementToExtractionResult(sb, "location", pk.getLocation()); SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy"); if (pk.getTimeStampDate() != null) { String timestamp = formatter.format(pk.getTimeStampDate().getTime()); sb = addElementToExtractionResult(sb, "timestamp-time", timestamp); } if (pk.getSignDate() != null) { String sign = formatter.format(pk.getSignDate().getTime()); sb = addElementToExtractionResult(sb, "sign-time", sign); } sb = addElementToExtractionResult(sb, "digest-algorithm", pk.getDigestAlgorithm()); sb = addElementToExtractionResult(sb, "hash-algorithm", pk.getHashAlgorithm()); sb = addElementToExtractionResult(sb, "covers-whole-document", Boolean.toString(fields.signatureCoversWholeDocument(name))); sb = addElementToExtractionResult(sb, "ft", widget.get(PdfName.FT).toString()); if (infoDictionary.contains(PdfName.CONTACTINFO)) sb = addElementToExtractionResult(sb, "contact-info", infoDictionary.getAsString(PdfName.CONTACTINFO).toString()); if (infoDictionary.contains(PdfName.FILTER)) sb = addElementToExtractionResult(sb, "filter", infoDictionary.get(PdfName.FILTER).toString()); if (infoDictionary.contains(PdfName.SUBFILTER)) sb = addElementToExtractionResult(sb, "subfilter", infoDictionary.get(PdfName.SUBFILTER).toString()); if (infoDictionary.contains(PdfName.LOCK)) sb = addElementToExtractionResult(sb, "lock", "true"); if (infoDictionary.contains(PdfName.CONTENTS)) { PdfString elementName = infoDictionary.getAsString(PdfName.CONTENTS); Files.write(outputContents, elementName.toUnicodeString().getBytes()); sb = addElementToExtractionResult(sb, "contents", filename + ".pkcs7"); } } catch (NoSuchFieldError e) { LOGGER.warn("DS information extraction did not execute properly"); } sb.append("</signature>"); } return sb; } private static StringBuilder addElementToExtractionResult(StringBuilder sb, String tagName, String value) { sb.append("<").append(tagName).append(">"); sb.append(value); sb.append("</").append(tagName).append(">\n"); return sb; } public static int countSignaturesPDF(Path file) { int counter = -1; try { PdfReader reader = new PdfReader(file.toAbsolutePath().toString()); AcroFields af = reader.getAcroFields(); ArrayList<String> names = af.getSignatureNames(); counter = names.size(); } catch (IOException e) { LOGGER.error("Error getting path of file {}", e.getMessage()); } return counter; } }