/** * 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.IOException; import java.io.InputStream; import java.nio.file.CopyOption; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.PrivateKey; import java.security.SignatureException; import java.security.cert.Certificate; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import javax.xml.crypto.MarshalException; import javax.xml.crypto.dsig.XMLSignatureException; import org.apache.commons.io.FileUtils; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.PackageAccess; import org.apache.poi.openxml4j.opc.PackagePart; import org.apache.poi.openxml4j.opc.PackageRelationship; import org.apache.poi.poifs.crypt.dsig.SignatureConfig; import org.apache.poi.poifs.crypt.dsig.SignatureInfo; import org.apache.poi.poifs.crypt.dsig.SignatureInfo.SignaturePart; public final class OOXMLSignatureUtils { private static final String SIGN_CONTENT_TYPE_OOXML = "application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml"; private static final String SIGN_REL_TYPE_OOXML = "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/origin"; /** Private empty constructor */ private OOXMLSignatureUtils() { // do nothing } public static String runDigitalSignatureVerify(Path input) throws IOException, GeneralSecurityException { boolean isValid = true; try (OPCPackage pkg = OPCPackage.open(input.toString(), PackageAccess.READ)) { SignatureConfig sic = new SignatureConfig(); sic.setOpcPackage(pkg); SignatureInfo si = new SignatureInfo(); si.setSignatureConfig(sic); Iterable<SignaturePart> it = si.getSignatureParts(); if (it != null) { for (SignaturePart sp : it) { isValid = isValid && sp.validate(); Set<Certificate> trustedRootCerts = new HashSet<>(); Set<Certificate> intermediateCerts = new HashSet<>(); List<X509Certificate> certChain = sp.getCertChain(); for (X509Certificate c : certChain) { c.checkValidity(); if (SignatureUtils.isCertificateSelfSigned(c)) { trustedRootCerts.add(c); } else { intermediateCerts.add(c); } } SignatureUtils.verifyCertificateChain(trustedRootCerts, intermediateCerts, certChain.get(0)); } } } catch (InvalidFormatException e) { return "Error opening a document file"; } catch (CertificateExpiredException e) { return "Contains expired certificates"; } catch (CertificateNotYetValidException e) { return "Contains certificates not yet valid"; } return isValid ? "Passed" : "Not passed"; } public static Map<Path, String> runDigitalSignatureExtract(Path input) throws SignatureException, IOException { Map<Path, String> paths = new HashMap<>(); try (ZipFile zipFile = new ZipFile(input.toString())) { Enumeration<?> enumeration; ZipEntry documentSignatureEntry = null; for (enumeration = zipFile.entries(); enumeration.hasMoreElements();) { ZipEntry entry = (ZipEntry) enumeration.nextElement(); if (entry.getName().startsWith("_xmlsignatures") && entry.getName().endsWith(".xml")) { documentSignatureEntry = entry; break; } } if (documentSignatureEntry != null) { try (InputStream zipStream = zipFile.getInputStream(documentSignatureEntry)) { Path extractedSignature = Files.createTempFile("extraction", ".xml"); String entryName = documentSignatureEntry.getName(); FileUtils.copyInputStreamToFile(zipStream, extractedSignature.toFile()); paths.put(extractedSignature, entryName.substring(entryName.lastIndexOf('/') + 1, entryName.lastIndexOf('.'))); } } } return paths; } public static void runDigitalSignatureStrip(Path input, Path output) throws IOException, InvalidFormatException { CopyOption[] copyOptions = new CopyOption[] {StandardCopyOption.REPLACE_EXISTING}; Files.copy(input, output, copyOptions); try (OPCPackage pkg = OPCPackage.open(output.toString(), PackageAccess.READ_WRITE)) { ArrayList<PackagePart> pps = pkg.getPartsByContentType(SIGN_CONTENT_TYPE_OOXML); for (PackagePart pp : pps) { pkg.removePart(pp); } ArrayList<PackagePart> ppct = pkg.getPartsByRelationshipType(SIGN_REL_TYPE_OOXML); for (PackagePart pp : ppct) { pkg.removePart(pp); } for (PackageRelationship r : pkg.getRelationships()) { if (r.getRelationshipType().equals(SIGN_REL_TYPE_OOXML)) { pkg.removeRelationship(r.getId()); } } } } public static Path runDigitalSignatureSign(Path input, String keystore, String alias, String password, String fileFormat) throws IOException, GeneralSecurityException, InvalidFormatException, XMLSignatureException, MarshalException { Path output = Files.createTempFile("signed", "." + fileFormat); CopyOption[] copyOptions = new CopyOption[] {StandardCopyOption.REPLACE_EXISTING}; Files.copy(input, output, copyOptions); 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()); X509Certificate x509 = (X509Certificate) ks.getCertificate(alias); SignatureConfig signatureConfig = new SignatureConfig(); signatureConfig.setKey(pk); signatureConfig.setSigningCertificateChain(Collections.singletonList(x509)); try (OPCPackage pkg = OPCPackage.open(output.toString(), PackageAccess.READ_WRITE)) { signatureConfig.setOpcPackage(pkg); SignatureInfo si = new SignatureInfo(); si.setSignatureConfig(signatureConfig); si.confirmSignature(); // boolean b = si.verifySignature(); } } return output; } }