/*
* XAdES4j - A Java library for generation and verification of XAdES signatures.
* Copyright (C) 2010 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 xades4j.algorithms.Algorithm;
import com.google.inject.Inject;
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
import org.apache.xml.security.signature.ObjectContainer;
import org.apache.xml.security.signature.Reference;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.signature.XMLSignatureException;
import org.apache.xml.security.transforms.Transforms;
import org.apache.xml.security.utils.resolver.ResourceResolver;
import org.apache.xml.security.utils.resolver.implementations.ResolverAnonymous;
import org.w3c.dom.Document;
import xades4j.UnsupportedAlgorithmException;
import xades4j.properties.DataObjectDesc;
import xades4j.providers.AlgorithmsProviderEx;
import xades4j.utils.TransformUtils;
import xades4j.xml.marshalling.algorithms.AlgorithmsParametersMarshallingProvider;
/**
* Helper class that processes a set of data object descriptions.
*
* @author Luís
*/
class SignedDataObjectsProcessor
{
private final AlgorithmsProviderEx algorithmsProvider;
private final AlgorithmsParametersMarshallingProvider algorithmsParametersMarshaller;
@Inject
SignedDataObjectsProcessor(AlgorithmsProviderEx algorithmsProvider, AlgorithmsParametersMarshallingProvider algorithmsParametersMarshaller)
{
this.algorithmsProvider = algorithmsProvider;
this.algorithmsParametersMarshaller = algorithmsParametersMarshaller;
}
/**
* Processes the signed data objects and adds the corresponding {@code Reference}s
* and {@code Object}s to the signature. This method must be invoked before
* adding any other {@code Reference}s to the signature.
*
* @return the reference mappings resulting from the data object descriptions.
*
* @throws UnsupportedAlgorithmException
* @throws IllegalStateException if the signature already contains {@code Reference}s
*/
Map<DataObjectDesc, Reference> process(
SignedDataObjects signedDataObjects,
XMLSignature xmlSignature) throws UnsupportedAlgorithmException
{
if(xmlSignature.getSignedInfo().getLength() != 0)
{
throw new IllegalStateException("XMLSignature already contais references");
}
for (ResourceResolver resolver : signedDataObjects.getResourceResolvers())
{
xmlSignature.addResourceResolver(resolver);
}
Collection<DataObjectDesc> dataObjsDescs = signedDataObjects.getDataObjectsDescs();
Map<DataObjectDesc, Reference> referenceMappings = new IdentityHashMap<DataObjectDesc, Reference>(dataObjsDescs.size());
String refUri, refType;
Transforms transforms;
String digestMethodUri = this.algorithmsProvider.getDigestAlgorithmForDataObjsReferences();
boolean hasNullURIReference = false;
/**/
try
{
for (DataObjectDesc dataObjDesc : dataObjsDescs)
{
transforms = processTransforms(dataObjDesc, xmlSignature.getDocument());
if (dataObjDesc instanceof DataObjectReference)
{
// If the data object info is a DataObjectReference, the Reference uri
// and type are the ones specified on the object.
DataObjectReference dataObjRef = (DataObjectReference) dataObjDesc;
refUri = dataObjRef.getUri();
refType = dataObjRef.getType();
}
else if (dataObjDesc instanceof EnvelopedXmlObject)
{
// If the data object info is a EnvelopedXmlObject we need to create a
// XMLObject to embed it. The Reference uri will refer the new
// XMLObject's id.
EnvelopedXmlObject envXmlObj = (EnvelopedXmlObject) dataObjDesc;
refUri = String.format("%s-object%d", xmlSignature.getId(), xmlSignature.getObjectLength());
refType = Reference.OBJECT_URI;
ObjectContainer xmlObj = new ObjectContainer(xmlSignature.getDocument());
xmlObj.setId(refUri);
xmlObj.appendChild(envXmlObj.getContent());
xmlObj.setMimeType(envXmlObj.getMimeType());
xmlObj.setEncoding(envXmlObj.getEncoding());
xmlSignature.appendObject(xmlObj);
refUri = '#' + refUri;
}
else if (dataObjDesc instanceof AnonymousDataObjectReference)
{
if (hasNullURIReference)
{
// This shouldn't happen because SignedDataObjects does the validation.
throw new IllegalStateException("Multiple AnonymousDataObjectReference detected");
}
hasNullURIReference = true;
refUri = refType = null;
AnonymousDataObjectReference anonymousRef = (AnonymousDataObjectReference) dataObjDesc;
xmlSignature.addResourceResolver(new ResolverAnonymous(anonymousRef.getDataStream()));
}
else
{
throw new ClassCastException("Unsupported SignedDataObjectDesc. Must be one of DataObjectReference, EnvelopedXmlObject and AnonymousDataObjectReference");
}
// Add the Reference. References need an ID because data object
// properties may refer them.
xmlSignature.addDocument(
refUri,
transforms,
digestMethodUri,
String.format("%s-ref%d", xmlSignature.getId(), referenceMappings.size()), // id
refType);
// SignedDataObjects doesn't allow repeated instances, so there's no
// need to check for duplicate entries on the map.
Reference ref = xmlSignature.getSignedInfo().item(referenceMappings.size());
referenceMappings.put(dataObjDesc, ref);
}
} catch (XMLSignatureException ex)
{
// -> xmlSignature.appendObject(xmlObj): not thrown when signing.
// -> xmlSignature.addDocument(...): appears to be thrown when the digest
// algorithm is not supported.
throw new UnsupportedAlgorithmException(
"Digest algorithm not supported in the XML Signature provider",
digestMethodUri, ex);
} catch (org.apache.xml.security.exceptions.XMLSecurityException ex)
{
// -> xmlSignature.getSignedInfo().item(...): shouldn't be thrown
// when signing.
throw new IllegalStateException(ex);
}
return Collections.unmodifiableMap(referenceMappings);
}
private Transforms processTransforms(
DataObjectDesc dataObjDesc,
Document document) throws UnsupportedAlgorithmException
{
Collection<Algorithm> dObjTransfs = dataObjDesc.getTransforms();
if (dObjTransfs.isEmpty())
{
return null;
}
return TransformUtils.createTransforms(document, this.algorithmsParametersMarshaller, dObjTransfs);
}
}