Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support custom XML element IDs #303

Merged
merged 2 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* XAdES4j - A Java library for generation and verification of XAdES signatures.
* Copyright (C) 2024 Luis Goncalves.
*
* XAdES4j is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or any later version.
*
* XAdES4j is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with XAdES4j. If not, see <http://www.gnu.org/licenses/>.
*/

package xades4j.production;

import java.util.UUID;

final class DefaultElementIdGeneratorFactory implements ElementIdGeneratorFactory
{
@Override
public ElementIdGenerator create()
{
return (namespace, name) -> {
return name.toLowerCase() + "-" + UUID.randomUUID();
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ protected void configure()
bind(HttpTsaConfiguration.class).toProvider(() -> {
throw new IllegalStateException("HttpTsaConfiguration must be configured in the profile in order to use an HTTP-based time-stamp token provider.");
});
bind(ElementIdGeneratorFactory.class).to(DefaultElementIdGeneratorFactory.class);

// PropertiesDataObjectsGenerator is not configurable but the individual
// generators may have dependencies.
Expand Down
57 changes: 57 additions & 0 deletions src/main/java/xades4j/production/ElementIdGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* XAdES4j - A Java library for generation and verification of XAdES signatures.
* Copyright (C) 2024 Luis Goncalves.
*
* XAdES4j is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or any later version.
*
* XAdES4j is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with XAdES4j. If not, see <http://www.gnu.org/licenses/>.
*/

package xades4j.production;

import java.util.UUID;

/**
* Generates IDs for XML elements in a given signing operation.
*/
public interface ElementIdGenerator
{
/**
* Generate an ID for an XML element.
*
* @param namespace the element namespace
* @param name the element name
* @return the ID
*/
String generateId(String namespace, String name);

/**
* Gets a {@link ElementIdGenerator} that uses a UUID for each requested ID.
*/
static ElementIdGenerator uuid()
{
return uuid(null, null);
}

/**
* Gets a {@link ElementIdGenerator} that uses a UUID for each requested ID, optionally using a constant prefix
* and/or suffix.
*
* @param prefix the ID prefix (may be null)
* @param suffix the ID suffix (may be null)
*/
static ElementIdGenerator uuid(String prefix, String suffix)
{
final String p = prefix == null ? "" : prefix;
final String s = suffix == null ? "" : suffix;
return (ns, n) -> p + UUID.randomUUID() + s;
}
}
55 changes: 55 additions & 0 deletions src/main/java/xades4j/production/ElementIdGeneratorFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* XAdES4j - A Java library for generation and verification of XAdES signatures.
* Copyright (C) 2024 Luis Goncalves.
*
* XAdES4j is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or any later version.
*
* XAdES4j is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with XAdES4j. If not, see <http://www.gnu.org/licenses/>.
*/

package xades4j.production;

import java.util.UUID;

/**
* A factory of {@link ElementIdGenerator}.
*/
public interface ElementIdGeneratorFactory
{
/**
* Create a new {@link ElementIdGenerator}. This method is invoked once for each signing operation and the returned
* instance is used to obtain element IDs during that operation. This allows for scenarios where all the element IDs
* share a common base.
*
* @return the ID generator
*/
ElementIdGenerator create();

/**
* Gets a {@link ElementIdGeneratorFactory} that uses a UUID for each requested ID.
*/
static ElementIdGeneratorFactory uuid()
{
return ElementIdGenerator::uuid;
}

/**
* Gets a {@link ElementIdGeneratorFactory} that uses a UUID for each requested ID, optionally using a constant
* prefix and/or suffix.
*
* @param prefix the ID prefix (may be null)
* @param suffix the ID suffix (may be null)
*/
static ElementIdGeneratorFactory uuid(String prefix, String suffix)
{
return () -> ElementIdGenerator.uuid(prefix, suffix);
}
}
11 changes: 7 additions & 4 deletions src/main/java/xades4j/production/KeyInfoBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
import xades4j.utils.TransformUtils;
import xades4j.xml.marshalling.algorithms.AlgorithmsParametersMarshallingProvider;

import static xades4j.production.SignerBES.idFor;

/**
* Helper class that creates the {@code ds:KeyInfo} element accordingly to some
* signature options. The signing certificate validity and key usages are
Expand Down Expand Up @@ -60,15 +62,16 @@ class KeyInfoBuilder

void buildKeyInfo(
List<X509Certificate> signingCertificateChain,
XMLSignature xmlSig) throws KeyingDataException, UnsupportedAlgorithmException
XMLSignature xmlSig,
ElementIdGenerator idGenerator) throws KeyingDataException, UnsupportedAlgorithmException
{
X509Certificate signingCertificate = getSigningCertificate(signingCertificateChain);

addSigningCertificateElements(signingCertificateChain, signingCertificate, xmlSig);

addPublicKey(signingCertificate, xmlSig);

addKeyInfoReference(xmlSig);
addKeyInfoReference(xmlSig, idGenerator);
}

private void addSigningCertificateElements(List<X509Certificate> signingCertificateChain, X509Certificate signingCertificate, XMLSignature xmlSig) throws KeyingDataException
Expand Down Expand Up @@ -119,13 +122,13 @@ private void addPublicKey(X509Certificate signingCertificate, XMLSignature xmlSi
}
}

private void addKeyInfoReference(XMLSignature xmlSig) throws UnsupportedAlgorithmException
private void addKeyInfoReference(XMLSignature xmlSig, ElementIdGenerator idGenerator) throws UnsupportedAlgorithmException
{
if (this.basicSignatureOptions.signKeyInfo())
{
try
{
String keyInfoId = xmlSig.getId() + "-keyinfo";
String keyInfoId = idFor(xmlSig.getKeyInfo(), idGenerator);
xmlSig.getKeyInfo().setId(keyInfoId);

// Use same canonicalization URI as specified in the ds:CanonicalizationMethod for Signature.
Expand Down
25 changes: 15 additions & 10 deletions src/main/java/xades4j/production/SignedDataObjectsProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
import java.util.Map;
import java.util.Set;

import static xades4j.production.SignerBES.idFor;

/**
* Helper class that processes a set of data object descriptions.
*
Expand Down Expand Up @@ -80,7 +82,8 @@ public Result(Map<DataObjectDesc, Reference> referenceMappings, Set<Manifest> ma
*/
SignedDataObjectsProcessor.Result process(
SignedDataObjects signedDataObjects,
XMLSignature xmlSignature) throws UnsupportedAlgorithmException {
XMLSignature xmlSignature,
ElementIdGenerator idGenerator) throws UnsupportedAlgorithmException {
if (xmlSignature.getSignedInfo().getLength() != 0)
{
throw new IllegalStateException("XMLSignature already contains references");
Expand All @@ -89,19 +92,19 @@ SignedDataObjectsProcessor.Result process(
return process(
signedDataObjects.getDataObjectsDescs(),
xmlSignature.getSignedInfo(),
xmlSignature.getId(),
signedDataObjects.getResourceResolvers(),
xmlSignature,
false);
false,
idGenerator);
}

private SignedDataObjectsProcessor.Result process(
Collection<? extends DataObjectDesc> dataObjects,
Manifest container,
String idPrefix,
List<ResourceResolverSpi> resourceResolvers,
XMLSignature xmlSignature,
boolean hasNullURIReference) throws UnsupportedAlgorithmException {
boolean hasNullURIReference,
ElementIdGenerator idGenerator) throws UnsupportedAlgorithmException {
Map<DataObjectDesc, Reference> referenceMappings = new IdentityHashMap<>(dataObjects.size());
Set<Manifest> manifests = new HashSet<>();

Expand Down Expand Up @@ -134,9 +137,9 @@ else if (dataObjDesc instanceof EnvelopedXmlObject)
// If the data object info is a EnvelopedXmlObject we need to create a ds:Object to embed it.
// The Reference uri will refer the new ds:Object's id.
EnvelopedXmlObject envXmlObj = (EnvelopedXmlObject) dataObjDesc;
String xmlObjId = String.format("%s-object%d", idPrefix, index);

ObjectContainer xmlObj = new ObjectContainer(container.getDocument());
String xmlObjId = idFor(xmlObj, idGenerator);
xmlObj.setId(xmlObjId);
xmlObj.appendChild(envXmlObj.getContent());
xmlObj.setMimeType(envXmlObj.getMimeType());
Expand Down Expand Up @@ -164,17 +167,18 @@ else if (dataObjDesc instanceof EnvelopedManifest)
// If the data object info is a EnvelopedManifest we need to create a ds:Manifest and a ds:Object
// to embed it. The Reference uri will refer the manifest's id.
EnvelopedManifest envManifest = (EnvelopedManifest) dataObjDesc;
String xmlManifestId = String.format("%s-manifest%d", idPrefix, index);

Manifest xmlManifest = new Manifest(container.getDocument());
String xmlManifestId = idFor(xmlManifest, idGenerator);
xmlManifest.setId(xmlManifestId);

SignedDataObjectsProcessor.Result manifestResult = process(
envManifest.getDataObjects(),
xmlManifest,
xmlManifestId,
resourceResolvers,
xmlSignature,
hasNullURIReference);
hasNullURIReference,
idGenerator);

ObjectContainer xmlObj = new ObjectContainer(container.getDocument());
xmlObj.appendChild(xmlManifest.getElement());
Expand All @@ -199,12 +203,13 @@ else if (dataObjDesc instanceof EnvelopedManifest)
refUri,
transforms,
digestMethodUri,
String.format("%s-ref%d", idPrefix, index), // id
null,
refType);

// SignedDataObjects and EnvelopedManifest don't allow repeated instances, so there's no
// need to check for duplicate entries on the map.
Reference ref = container.item(index);
ref.setId(idFor(ref, idGenerator));
referenceMappings.put(dataObjDesc, ref);
}

Expand Down
30 changes: 22 additions & 8 deletions src/main/java/xades4j/production/SignerBES.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class SignerBES implements XadesSigner
private final SignedPropertiesMarshaller signedPropsMarshaller;
private final UnsignedPropertiesMarshaller unsignedPropsMarshaller;
private final AlgorithmsParametersMarshallingProvider algorithmsParametersMarshaller;
private final ElementIdGeneratorFactory idGeneratorFactory;
/**/
private final KeyInfoBuilder keyInfoBuilder;
private final QualifyingPropertiesProcessor qualifPropsProcessor;
Expand All @@ -101,13 +102,14 @@ protected SignerBES(
SignedPropertiesMarshaller signedPropsMarshaller,
UnsignedPropertiesMarshaller unsignedPropsMarshaller,
AlgorithmsParametersMarshallingProvider algorithmsParametersMarshaller,
X500NameStyleProvider x500NameStyleProvider)
X500NameStyleProvider x500NameStyleProvider,
ElementIdGeneratorFactory idGeneratorFactory)
{
if (ObjectUtils.anyNull(
keyingProvider, signatureAlgorithms, basicSignatureOptions,
signaturePropsProvider, dataObjPropsProvider, propsDataObjectsGenerator,
signedPropsMarshaller, unsignedPropsMarshaller, algorithmsParametersMarshaller,
x500NameStyleProvider))
x500NameStyleProvider, idGeneratorFactory))
{
throw new NullPointerException("One or more arguments are null");
}
Expand All @@ -120,6 +122,7 @@ protected SignerBES(
this.unsignedPropsMarshaller = unsignedPropsMarshaller;
this.algorithmsParametersMarshaller = algorithmsParametersMarshaller;
this.dataObjectDescsProcessor = dataObjectDescsProcessor;
this.idGeneratorFactory = idGeneratorFactory;
this.keyInfoBuilder = new KeyInfoBuilder(basicSignatureOptions, signatureAlgorithms, algorithmsParametersMarshaller, x500NameStyleProvider);
this.qualifPropsProcessor = new QualifyingPropertiesProcessor(signaturePropsProvider, dataObjPropsProvider);
}
Expand Down Expand Up @@ -154,10 +157,8 @@ public final XadesSignatureResult sign(
this.basicSignatureOptions.ensureValid();

Document signatureDocument = DOMHelper.getOwnerDocument(referenceNode);
ElementIdGenerator idGenerator = this.idGeneratorFactory.create();

// Generate unique identifiers for the Signature and the SignedProperties.
String signatureId = String.format("xmldsig-%s", UUID.randomUUID());
String signedPropsId = String.format("%s-signedprops", signatureId);

// Signing certificate chain (may contain only the signing certificate).
List<X509Certificate> signingCertificateChain = this.keyingProvider.getSigningCertificateChain();
Expand All @@ -173,6 +174,7 @@ public final XadesSignatureResult sign(
signedDataObjects.getBaseUri(),
signingCertificate.getPublicKey().getAlgorithm());

String signatureId = idFor(signature, idGenerator);
signature.setId(signatureId);

/* References */
Expand All @@ -181,10 +183,11 @@ public final XadesSignatureResult sign(
// are added to the signature.
SignedDataObjectsProcessor.Result signedDataObjectsResult = this.dataObjectDescsProcessor.process(
signedDataObjects,
signature);
signature,
idGenerator);

/* ds:KeyInfo */
this.keyInfoBuilder.buildKeyInfo(signingCertificateChain, signature);
this.keyInfoBuilder.buildKeyInfo(signingCertificateChain, signature, idGenerator);

/* QualifyingProperties element */
// Create the QualifyingProperties element
Expand Down Expand Up @@ -239,6 +242,7 @@ public final XadesSignatureResult sign(
// Marshal the signed properties data to the QualifyingProperties node.
this.signedPropsMarshaller.marshal(signedPropsData, qualifyingPropsElem);
Element signedPropsElem = DOMHelper.getFirstChildElement(qualifyingPropsElem);
String signedPropsId = idFor(signedPropsElem, idGenerator);
DOMHelper.setIdAsXmlId(signedPropsElem, signedPropsId);

// SignedProperties reference
Expand Down Expand Up @@ -285,7 +289,7 @@ public final XadesSignatureResult sign(
Element sigValueElem = DOMHelper.getFirstDescendant(
signature.getElement(),
Constants.SignatureSpecNS, Constants._TAG_SIGNATUREVALUE);
DOMHelper.setIdAsXmlId(sigValueElem, String.format("%s-sigvalue", signatureId));
DOMHelper.setIdAsXmlId(sigValueElem, idFor(sigValueElem, idGenerator));

/* Marshal unsigned properties */
// Generate the unsigned properties data objects. The data objects structure
Expand Down Expand Up @@ -385,4 +389,14 @@ protected void getFormatSpecificSignatureProperties(
formatSpecificSignedSigProps.add(scp);
}
}

public static String idFor(ElementProxy elementProxy, ElementIdGenerator idGenerator)
{
return idGenerator.generateId(elementProxy.getBaseNamespace(), elementProxy.getBaseLocalName());
}

public static String idFor(Element element, ElementIdGenerator idGenerator)
{
return idGenerator.generateId(element.getNamespaceURI(), element.getLocalName());
}
}
3 changes: 2 additions & 1 deletion src/main/java/xades4j/production/SignerC.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,10 @@ protected SignerC(
UnsignedPropertiesMarshaller unsignedPropsMarshaller,
AlgorithmsParametersMarshallingProvider algorithmsParametersMarshaller,
X500NameStyleProvider x500NameStyleProvider,
ElementIdGeneratorFactory idGeneratorFactory,
Optional<SignaturePolicyInfoProvider> policyInfoProvider)
{
super(keyingProvider, signatureAlgorithms, basicSignatureOptions, dataObjectDescsProcessor, signaturePropsProvider, dataObjPropsProvider, propsDataObjectsGenerator, signedPropsMarshaller, unsignedPropsMarshaller, algorithmsParametersMarshaller, x500NameStyleProvider, policyInfoProvider);
super(keyingProvider, signatureAlgorithms, basicSignatureOptions, dataObjectDescsProcessor, signaturePropsProvider, dataObjPropsProvider, propsDataObjectsGenerator, signedPropsMarshaller, unsignedPropsMarshaller, algorithmsParametersMarshaller, x500NameStyleProvider, idGeneratorFactory, policyInfoProvider);
if (null == validationDataProvider)
throw new NullPointerException("ValidationDataProvider is null");

Expand Down
Loading