/*
* 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 org.apache.xml.security.c14n.Canonicalizer;
import org.apache.xml.security.c14n.InvalidCanonicalizerException;
import org.apache.xml.security.transforms.TransformationException;
import org.apache.xml.security.transforms.Transforms;
import xades4j.properties.QualifyingProperties;
import xades4j.properties.DataObjectDesc;
import com.google.inject.Inject;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.apache.xml.security.exceptions.XMLSecurityException;
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.utils.Constants;
import org.apache.xml.security.utils.ElementProxy;
import org.apache.xml.security.utils.XMLUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import xades4j.algorithms.Algorithm;
import xades4j.properties.QualifyingProperty;
import xades4j.properties.SignedSignatureProperty;
import xades4j.properties.SigningCertificateProperty;
import xades4j.properties.UnsignedSignatureProperty;
import xades4j.UnsupportedAlgorithmException;
import xades4j.XAdES4jException;
import xades4j.XAdES4jXMLSigException;
import xades4j.properties.data.SigAndDataObjsPropertiesData;
import xades4j.providers.AlgorithmsProviderEx;
import xades4j.providers.BasicSignatureOptionsProvider;
import xades4j.providers.DataObjectPropertiesProvider;
import xades4j.providers.KeyingDataProvider;
import xades4j.providers.SignaturePropertiesProvider;
import xades4j.providers.SigningCertChainException;
import xades4j.utils.CanonicalizerUtils;
import xades4j.utils.DOMHelper;
import xades4j.utils.ObjectUtils;
import xades4j.utils.StringUtils;
import xades4j.utils.TransformUtils;
import xades4j.xml.marshalling.SignedPropertiesMarshaller;
import xades4j.xml.marshalling.UnsignedPropertiesMarshaller;
import xades4j.xml.marshalling.algorithms.AlgorithmsParametersMarshallingProvider;
/**
* Base logic for producing XAdES signatures (XAdES-BES).
* @author Luís
*/
class SignerBES implements XadesSigner
{
static
{
Init.initXMLSec();
}
/**/
private final KeyingDataProvider keyingProvider;
private final AlgorithmsProviderEx algorithmsProvider;
private final SignedDataObjectsProcessor dataObjectDescsProcessor;
private final PropertiesDataObjectsGenerator propsDataObjectsGenerator;
private final SignedPropertiesMarshaller signedPropsMarshaller;
private final UnsignedPropertiesMarshaller unsignedPropsMarshaller;
private final AlgorithmsParametersMarshallingProvider algorithmsParametersMarshaller;
/**/
private final KeyInfoBuilder keyInfoBuilder;
private final QualifyingPropertiesProcessor qualifPropsProcessor;
@Inject
protected SignerBES(
KeyingDataProvider keyingProvider,
AlgorithmsProviderEx algorithmsProvider,
BasicSignatureOptionsProvider basicSignatureOptionsProvider,
SignedDataObjectsProcessor dataObjectDescsProcessor,
SignaturePropertiesProvider signaturePropsProvider,
DataObjectPropertiesProvider dataObjPropsProvider,
PropertiesDataObjectsGenerator propsDataObjectsGenerator,
SignedPropertiesMarshaller signedPropsMarshaller,
UnsignedPropertiesMarshaller unsignedPropsMarshaller,
AlgorithmsParametersMarshallingProvider algorithmsParametersMarshaller)
{
if (ObjectUtils.anyNull(
keyingProvider, algorithmsProvider,
signaturePropsProvider, dataObjPropsProvider, propsDataObjectsGenerator,
signedPropsMarshaller, unsignedPropsMarshaller, algorithmsParametersMarshaller))
{
throw new NullPointerException("One or more arguments are null");
}
this.keyingProvider = keyingProvider;
this.algorithmsProvider = algorithmsProvider;
this.propsDataObjectsGenerator = propsDataObjectsGenerator;
this.signedPropsMarshaller = signedPropsMarshaller;
this.unsignedPropsMarshaller = unsignedPropsMarshaller;
this.algorithmsParametersMarshaller = algorithmsParametersMarshaller;
this.dataObjectDescsProcessor = dataObjectDescsProcessor;
this.keyInfoBuilder = new KeyInfoBuilder(basicSignatureOptionsProvider, algorithmsProvider, algorithmsParametersMarshaller);
this.qualifPropsProcessor = new QualifyingPropertiesProcessor(signaturePropsProvider, dataObjPropsProvider);
}
@Override
public final XadesSignatureResult sign(
SignedDataObjects signedDataObjects,
Node parent) throws XAdES4jException
{
return sign(signedDataObjects, parent, SignatureAppendingStrategies.AsLastChild);
}
@Override
public final XadesSignatureResult sign(
SignedDataObjects signedDataObjects,
Node referenceNode,
SignatureAppendingStrategy appendingStrategy) throws XAdES4jException
{
if (null == referenceNode)
{
throw new NullPointerException("Reference node node cannot be null");
}
if (null == signedDataObjects)
{
throw new NullPointerException("References cannot be null");
}
if (signedDataObjects.isEmpty())
{
throw new IllegalArgumentException("Data objects list is empty");
}
Document signatureDocument = DOMHelper.getOwnerDocument(referenceNode);
// 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();
if (null == signingCertificateChain || signingCertificateChain.isEmpty())
{
throw new SigningCertChainException("Signing certificate not provided");
}
X509Certificate signingCertificate = signingCertificateChain.get(0);
// The XMLSignature (ds:Signature).
XMLSignature signature = createSignature(
signatureDocument,
signedDataObjects.getBaseUri(),
signingCertificate.getPublicKey().getAlgorithm());
signature.setId(signatureId);
/* References */
// Process the data object descriptions to get the References and mappings.
// After this call all the signed data objects References and XMLObjects
// are added to the signature.
Map<DataObjectDesc, Reference> referenceMappings = this.dataObjectDescsProcessor.process(
signedDataObjects,
signature);
/* ds:KeyInfo */
this.keyInfoBuilder.buildKeyInfo(signingCertificate, signature);
/* QualifyingProperties element */
// Create the QualifyingProperties element
Element qualifyingPropsElem = ElementProxy.createElementForFamily(
signature.getDocument(),
QualifyingProperty.XADES_XMLNS, QualifyingProperty.QUALIFYING_PROPS_TAG);
qualifyingPropsElem.setAttributeNS(null, QualifyingProperty.TARGET_ATTR, '#' + signatureId);
qualifyingPropsElem.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:xades141", QualifyingProperty.XADESV141_XMLNS);
// ds:Object to contain QualifyingProperties
ObjectContainer qPropsXmlObj = new ObjectContainer(signature.getDocument());
qPropsXmlObj.appendChild(qualifyingPropsElem);
try
{
signature.appendObject(qPropsXmlObj);
} catch (XMLSignatureException ex)
{
// -> xmlSignature.appendObject(xmlObj): not thrown when signing.
throw new IllegalStateException(ex);
}
/* Collect the properties */
// Get the format specific signature properties.
Collection<SignedSignatureProperty> fsssp = new ArrayList<SignedSignatureProperty>(2);
Collection<UnsignedSignatureProperty> fsusp = new ArrayList<UnsignedSignatureProperty>(2);
getFormatSpecificSignatureProperties(fsssp, fsusp, signingCertificateChain);
// Gather all the signature and data objects properties.
QualifyingProperties qualifProps = qualifPropsProcessor.getQualifyingProperties(
signedDataObjects, fsssp, fsusp);
try
{
// The signature needs to be appended to the document from now on because
// property data generation may need to dereference same-document data
// object references.
appendingStrategy.append(signature.getElement(), referenceNode);
/* Signed properties */
// Create the context for signed properties data objects generation.
PropertiesDataGenerationContext propsDataGenCtx = new PropertiesDataGenerationContext(
signedDataObjects.getDataObjectsDescs(),
referenceMappings,
signatureDocument);
// Generate the signed properties data objects. The data objects structure
// is verifier in the process.
SigAndDataObjsPropertiesData signedPropsData = this.propsDataObjectsGenerator.generateSignedPropertiesData(
qualifProps.getSignedProperties(),
propsDataGenCtx);
// Marshal the signed properties data to the QualifyingProperties node.
this.signedPropsMarshaller.marshal(signedPropsData, qualifyingPropsElem);
Element signedPropsElem = DOMHelper.getFirstChildElement(qualifyingPropsElem);
DOMHelper.setIdAsXmlId(signedPropsElem, signedPropsId);
// SignedProperties reference
// XAdES 6.3.1: "In order to protect the properties with the signature,
// a ds:Reference element MUST be added to the XMLDSIG signature (...)
// composed in such a way that it uses the SignedProperties element (...)
// as the input for computing its corresponding digest. Additionally,
// (...) use the Type attribute of this particular ds:Reference element,
// with its value set to: http://uri.etsi.org/01903#SignedProperties."
String digestAlgUri = algorithmsProvider.getDigestAlgorithmForDataObjsReferences();
if (StringUtils.isNullOrEmptyString(digestAlgUri))
{
throw new NullPointerException("Digest algorithm URI not provided");
}
// Use same canonicalization URI as specified in the ds:CanonicalizationMethod for Signature.
Algorithm canonAlg = this.algorithmsProvider.getCanonicalizationAlgorithmForSignature();
try
{
CanonicalizerUtils.checkC14NAlgorithm(canonAlg);
Transforms transforms = TransformUtils.createTransforms(canonAlg, this.algorithmsParametersMarshaller, signatureDocument);
signature.addDocument('#' + signedPropsId, transforms, digestAlgUri, null, QualifyingProperty.SIGNED_PROPS_TYPE_URI);
} catch (XMLSignatureException ex)
{
// Seems to be thrown when the digest algorithm is not supported. In
// this case, if it wasn't thrown when processing the data objects it
// shouldn't be thrown now!
throw new UnsupportedAlgorithmException(
"Digest algorithm not supported in the XML Signature provider",
digestAlgUri, ex);
}
// Apply the signature
try
{
PrivateKey signingKey = keyingProvider.getSigningKey(signingCertificate);
signature.sign(signingKey);
}
catch (XMLSignatureException ex)
{
throw new XAdES4jXMLSigException(ex.getMessage(), ex);
}
// Set the ds:SignatureValue id.
Element sigValueElem = DOMHelper.getFirstDescendant(
signature.getElement(),
Constants.SignatureSpecNS, Constants._TAG_SIGNATUREVALUE);
DOMHelper.setIdAsXmlId(sigValueElem, String.format("%s-sigvalue", signatureId));
/* Marshal unsigned properties */
// Generate the unsigned properties data objects. The data objects structure
// is verifier in the process.
propsDataGenCtx.setTargetXmlSignature(signature);
SigAndDataObjsPropertiesData unsignedPropsData = this.propsDataObjectsGenerator.generateUnsignedPropertiesData(
qualifProps.getUnsignedProperties(),
propsDataGenCtx);
// Marshal the unsigned properties to the final QualifyingProperties node.
this.unsignedPropsMarshaller.marshal(unsignedPropsData, qualifyingPropsElem);
}
catch (XAdES4jException ex)
{
appendingStrategy.revert(signature.getElement(), referenceNode);
throw ex;
}
return new XadesSignatureResult(signature, qualifProps);
}
private XMLSignature createSignature(Document signatureDocument, String baseUri, String signingKeyAlgorithm) throws XAdES4jXMLSigException, UnsupportedAlgorithmException
{
Algorithm signatureAlg = this.algorithmsProvider.getSignatureAlgorithm(signingKeyAlgorithm);
if (null == signatureAlg)
{
throw new NullPointerException("Signature algorithm not provided");
}
Element signatureAlgElem = createElementForAlgorithm(signatureAlg, Constants._TAG_SIGNATUREMETHOD, signatureDocument);
Algorithm canonAlg = this.algorithmsProvider.getCanonicalizationAlgorithmForSignature();
if (null == canonAlg)
{
throw new NullPointerException("Canonicalization algorithm not provided");
}
Element canonAlgElem = createElementForAlgorithm(canonAlg, Constants._TAG_CANONICALIZATIONMETHOD, signatureDocument);
try
{
return new XMLSignature(signatureDocument, baseUri, signatureAlgElem, canonAlgElem);
} catch (XMLSecurityException ex)
{
// Following the code, doesn't seem to be thrown at all.
throw new XAdES4jXMLSigException(ex.getMessage(), ex);
}
}
private Element createElementForAlgorithm(Algorithm algorithm, String elementName, Document signatureDocument) throws UnsupportedAlgorithmException
{
Element algorithmElem = XMLUtils.createElementInSignatureSpace(signatureDocument, elementName);
algorithmElem.setAttributeNS(null, Constants._ATT_ALGORITHM, algorithm.getUri());
List<Node> algorithmParams = this.algorithmsParametersMarshaller.marshalParameters(algorithm, signatureDocument);
if (algorithmParams != null)
{
for (Node p : algorithmParams)
{
algorithmElem.appendChild(p);
}
}
return algorithmElem;
}
/**
* Override in subclasses to collect the signature properties that are mandatory
* in the corresponding format.
*/
protected void getFormatSpecificSignatureProperties(
Collection<SignedSignatureProperty> formatSpecificSignedSigProps,
Collection<UnsignedSignatureProperty> formatSpecificUnsignedSigProps,
List<X509Certificate> signingCertificateChain) throws XAdES4jException
{
SigningCertificateProperty scp = new SigningCertificateProperty(signingCertificateChain);
formatSpecificSignedSigProps.add(scp);
}
}