简体   繁体   中英

Digitally sign XML (XAdES ) with timestamping by streaming the XML

I have created an method that digitally signs an xml (XAdES with Timestamping) using xades4j library https://github.com/luisgoncalves/xades4j

Unfortunately the xml's are quite big sometimes (1,8 GB) and I was wondering if there is a way to do that by streaming the XML instead of creating a DOM and loading the whole document in memory. Is there a way? Can I do that with xades4j?

Below is the current code that signs the document using a DOM representation of the xml. The initial method that is called first is the signXml().

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyStoreException;

import javax.annotation.PostConstruct;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.safe.AbstractManager;
import com.safe.model.DirectPasswordProvider;

import xades4j.algorithms.ExclusiveCanonicalXMLWithoutComments;
import xades4j.production.Enveloped;
import xades4j.production.SignatureAlgorithms;
import xades4j.production.XadesSigner;
import xades4j.production.XadesTSigningProfile;
import xades4j.providers.KeyingDataProvider;
import xades4j.providers.impl.FileSystemKeyStoreKeyingDataProvider;
import xades4j.providers.impl.HttpTsaConfiguration;
import xades4j.providers.impl.KeyStoreKeyingDataProvider;
import xades4j.utils.DOMHelper;

@Component
public class FileOperationsManager extends AbstractManager {

    @Value("${certificates.digital-signature.filepath}")
    private String certPath;

    @Value("${certificates.digital-signature.password}")
    private String certPass;

    @Value("${certificates.digital-signature.type}")
    private String certType;

    private DocumentBuilder db;

    private TransformerFactory tf;

    @PostConstruct
    public void init() throws Exception {
        final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        this.db = dbf.newDocumentBuilder();
        this.tf = TransformerFactory.newInstance();
    }

    public Path signXml(final Path xmlFile, final Path targetDir) {

        final String baseName = FilenameUtils.getBaseName(xmlFile.getFileName().toString())
            .concat("_Signed")
            .concat(".")
            .concat(FilenameUtils.getExtension(xmlFile.getFileName().toString()));
        final Path target = Paths.get(targetDir.toString(), baseName);

        try (final FileInputStream fis = new FileInputStream(String.valueOf(xmlFile))) {

            final Document doc = this.parseDocument(fis);
            final Element elementToSign = doc.getDocumentElement();

            final SignatureAlgorithms algorithms = new SignatureAlgorithms()
                .withCanonicalizationAlgorithmForTimeStampProperties(new ExclusiveCanonicalXMLWithoutComments("ds", "xades"))
                .withCanonicalizationAlgorithmForSignature(new ExclusiveCanonicalXMLWithoutComments());

            final KeyingDataProvider kdp = this.createFileSystemKeyingDataProvider(certType, certPath, certPass, true);

            final XadesSigner signer = new XadesTSigningProfile(kdp)
                .withSignatureAlgorithms(algorithms)
                .with(new HttpTsaConfiguration("http://timestamp.digicert.com"))
                .newSigner();

            new Enveloped(signer).sign(elementToSign);

            this.exportDocument(doc, target);

        } catch (final FileNotFoundException e) {
            throw new RuntimeException();
        } catch (final Exception e) {
            throw new RuntimeException();
        }

        return target;
    }

    private FileSystemKeyStoreKeyingDataProvider createFileSystemKeyingDataProvider(
        final String keyStoreType,
        final String keyStorePath,
        final String keyStorePwd,
        final boolean returnFullChain) throws KeyStoreException {

        return FileSystemKeyStoreKeyingDataProvider
            .builder(keyStoreType, keyStorePath, KeyStoreKeyingDataProvider.SigningCertificateSelector.single())
            .storePassword(new DirectPasswordProvider(keyStorePwd))
            .entryPassword(new DirectPasswordProvider(keyStorePwd))
            .fullChain(returnFullChain)
            .build();
    }

    public Document parseDocument(final InputStream is) {
        try {
            final Document doc = this.db.parse(is);
            final Element elem = doc.getDocumentElement();
            DOMHelper.useIdAsXmlId(elem);
            return doc;
        } catch (final Exception e) {
            throw new RuntimeException();
        }
    }

    public void exportDocument(final Document doc, final Path target) {
        try (final FileOutputStream out = new FileOutputStream(target.toFile())) {
            this.tf.newTransformer().transform(
                new DOMSource(doc),
                new StreamResult(out));
        } catch (final Exception e) {
            throw new RuntimeException();
        }
    }

Unfortunately xades4j doesn't support streaming on the XML document to which the signature will be appended. I don't know if there are other alternative libraries that do.

A possible workaround using xades4j is to use a detached signature instead of an enveloped signature. The signature can be added to an empty XML document and the large XML file is explicitly added as a Reference to that signature.

xades4j delegates the core XML-DSIG handling to Apache Santuario, so if Santuario uses streaming for Reference resolution, this should avoid your issue. I'm not sure, though, but it may be worth testing.

https://github.com/luisgoncalves/xades4j/wiki/DefiningSignedResources

You may need to use a file URI and/or base URIs.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM