/*
* eID Applet Project.
* Copyright (C) 2009 FedICT.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version
* 3.0 as published by the Free Software Foundation.
*
* This software 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 this software; if not, see
* http://www.gnu.org/licenses/.
*/
package be.fedict.eid.applet.service.signer.facets;
import java.security.InvalidAlgorithmParameterException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.crypto.XMLStructure;
import javax.xml.crypto.dom.DOMStructure;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.DigestMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.Transform;
import javax.xml.crypto.dsig.XMLObject;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bouncycastle.jce.PrincipalUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import be.fedict.eid.applet.service.signer.DigestAlgo;
import be.fedict.eid.applet.service.signer.SignatureFacet;
import be.fedict.eid.applet.service.signer.jaxb.xades132.AnyType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.CertIDListType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.CertIDType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.ClaimedRolesListType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.DataObjectFormatType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.DigestAlgAndValueType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.IdentifierType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.ObjectFactory;
import be.fedict.eid.applet.service.signer.jaxb.xades132.ObjectIdentifierType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.QualifyingPropertiesType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.SigPolicyQualifiersListType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.SignaturePolicyIdType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.SignaturePolicyIdentifierType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.SignedDataObjectPropertiesType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.SignedPropertiesType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.SignedSignaturePropertiesType;
import be.fedict.eid.applet.service.signer.jaxb.xades132.SignerRoleType;
import be.fedict.eid.applet.service.signer.jaxb.xmldsig.DigestMethodType;
import be.fedict.eid.applet.service.signer.jaxb.xmldsig.X509IssuerSerialType;
import be.fedict.eid.applet.service.signer.time.Clock;
import be.fedict.eid.applet.service.signer.time.LocalClock;
/**
* XAdES Signature Facet. Implements XAdES v1.4.1 which is compatible with XAdES
* v1.3.2. The implemented XAdES format is XAdES-BES/EPES. It's up to another
* part of the signature service to upgrade the XAdES-BES to a XAdES-X-L.
*
* This implementation has been tested against an implementation that
* participated multiple ETSI XAdES plugtests.
*
* @author Frank Cornelis
* @see http://en.wikipedia.org/wiki/XAdES
*
*/
public class XAdESSignatureFacet implements SignatureFacet {
private static final Log LOG = LogFactory.getLog(XAdESSignatureFacet.class);
private static final String XADES_TYPE = "http://uri.etsi.org/01903#SignedProperties";
private final DatatypeFactory datatypeFactory;
private final ObjectFactory xadesObjectFactory;
private final be.fedict.eid.applet.service.signer.jaxb.xmldsig.ObjectFactory xmldsigObjectFactory;
private final Marshaller marshaller;
private final Clock clock;
private final DigestAlgo digestAlgorithm;
private final SignaturePolicyService signaturePolicyService;
private String idSignedProperties;
private boolean signaturePolicyImplied;
private final XAdESNamespacePrefixMapper xadesNamespacePrefixMapper;
private String role;
private boolean issuerNameNoReverseOrder = false;
private Map<String, String> dataObjectFormatMimeTypes;
/**
* Default constructor. Will use a local clock and "SHA-1" for digest
* algorithm.
*/
public XAdESSignatureFacet() {
this(new LocalClock());
}
/**
* Convenience constructor. Will use "SHA-1" for digest algorithm.
*
* @param clock
* the clock to be used for determining the xades:SigningTime
*/
public XAdESSignatureFacet(Clock clock) {
this(clock, DigestAlgo.SHA1);
}
/**
* Convenience constructor. Will use a local clock.
*
* @param digestAlgorithm
* the digest algorithm to be used for all required XAdES digest
* operations. Possible values: "SHA-1", "SHA-256", or "SHA-512".
*/
public XAdESSignatureFacet(DigestAlgo digestAlgorithm) {
this(new LocalClock(), digestAlgorithm);
}
/**
* Convenience constructor. Will use a local clock.
*
* @param digestAlgorithm
* the digest algorithm to be used for all required XAdES digest
* operations. Possible values: "SHA-1", "SHA-256", or "SHA-512".
* @param signaturePolicyService
* the optional signature policy service used for XAdES-EPES.
*/
public XAdESSignatureFacet(DigestAlgo digestAlgorithm, SignaturePolicyService signaturePolicyService) {
this(new LocalClock(), digestAlgorithm, signaturePolicyService);
}
/**
* Convenience constructor. Will use a local clock and "SHA-1" as digest
* algorithm.
*
* @param signaturePolicyService
* the optional signature policy service used for XAdES-EPES.
*/
public XAdESSignatureFacet(SignaturePolicyService signaturePolicyService) {
this(new LocalClock(), DigestAlgo.SHA1, signaturePolicyService);
}
/**
* Convenience constructor.
*
* @param clock
* the clock to be used for determining the xades:SigningTime
* @param digestAlgorithm
* the digest algorithm to be used for all required XAdES digest
* operations. Possible values: "SHA-1", "SHA-256", or "SHA-512".
*/
public XAdESSignatureFacet(Clock clock, DigestAlgo digestAlgorithm) {
this(clock, digestAlgorithm, null);
}
/**
* Main constructor.
*
* @param clock
* the clock to be used for determining the xades:SigningTime
* @param digestAlgorithm
* the digest algorithm to be used for all required XAdES digest
* operations. Possible values: "SHA-1", "SHA-256", or "SHA-512".
* @param signaturePolicyService
* the optional signature policy service used for XAdES-EPES.
*/
public XAdESSignatureFacet(Clock clock, DigestAlgo digestAlgorithm, SignaturePolicyService signaturePolicyService) {
this.clock = clock;
this.digestAlgorithm = digestAlgorithm;
this.signaturePolicyService = signaturePolicyService;
try {
this.datatypeFactory = DatatypeFactory.newInstance();
} catch (DatatypeConfigurationException e) {
throw new RuntimeException("datatype config error: " + e.getMessage(), e);
}
this.xadesObjectFactory = new ObjectFactory();
this.xmldsigObjectFactory = new be.fedict.eid.applet.service.signer.jaxb.xmldsig.ObjectFactory();
this.xadesNamespacePrefixMapper = new XAdESNamespacePrefixMapper();
try {
JAXBContext jaxbContext = JAXBContext.newInstance(ObjectFactory.class);
this.marshaller = jaxbContext.createMarshaller();
this.marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", this.xadesNamespacePrefixMapper);
} catch (JAXBException e) {
throw new RuntimeException("JAXB error: " + e.getMessage(), e);
}
this.dataObjectFormatMimeTypes = new HashMap<String, String>();
}
public void postSign(Element signatureElement, List<X509Certificate> signingCertificateChain) {
LOG.debug("postSign");
}
public void preSign(XMLSignatureFactory signatureFactory, Document document, String signatureId,
List<X509Certificate> signingCertificateChain, List<Reference> references, List<XMLObject> objects)
throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
LOG.debug("preSign");
// QualifyingProperties
QualifyingPropertiesType qualifyingProperties = this.xadesObjectFactory.createQualifyingPropertiesType();
qualifyingProperties.setTarget("#" + signatureId);
// SignedProperties
SignedPropertiesType signedProperties = this.xadesObjectFactory.createSignedPropertiesType();
String signedPropertiesId;
if (null != this.idSignedProperties) {
signedPropertiesId = this.idSignedProperties;
} else {
signedPropertiesId = signatureId + "-xades";
}
signedProperties.setId(signedPropertiesId);
qualifyingProperties.setSignedProperties(signedProperties);
// SignedSignatureProperties
SignedSignaturePropertiesType signedSignatureProperties = this.xadesObjectFactory
.createSignedSignaturePropertiesType();
signedProperties.setSignedSignatureProperties(signedSignatureProperties);
// SigningTime
GregorianCalendar signingTime = new GregorianCalendar(TimeZone.getTimeZone("Z"));
Date currentClockValue = this.clock.getTime();
signingTime.setTime(currentClockValue);
XMLGregorianCalendar xmlGregorianCalendar = this.datatypeFactory.newXMLGregorianCalendar(signingTime);
xmlGregorianCalendar.setMillisecond(DatatypeConstants.FIELD_UNDEFINED);
signedSignatureProperties.setSigningTime(xmlGregorianCalendar);
// SigningCertificate
if (null == signingCertificateChain || signingCertificateChain.isEmpty()) {
throw new RuntimeException("no signing certificate chain available");
}
X509Certificate signingCertificate = signingCertificateChain.get(0);
CertIDType signingCertificateId = getCertID(signingCertificate, this.xadesObjectFactory,
this.xmldsigObjectFactory, this.digestAlgorithm, this.issuerNameNoReverseOrder);
CertIDListType signingCertificates = this.xadesObjectFactory.createCertIDListType();
signingCertificates.getCert().add(signingCertificateId);
signedSignatureProperties.setSigningCertificate(signingCertificates);
// ClaimedRole
if (null != this.role && false == this.role.isEmpty()) {
SignerRoleType signerRole = this.xadesObjectFactory.createSignerRoleType();
signedSignatureProperties.setSignerRole(signerRole);
ClaimedRolesListType claimedRolesList = this.xadesObjectFactory.createClaimedRolesListType();
signerRole.setClaimedRoles(claimedRolesList);
AnyType claimedRole = this.xadesObjectFactory.createAnyType();
claimedRole.getContent().add(this.role);
claimedRolesList.getClaimedRole().add(claimedRole);
}
// XAdES-EPES
if (null != this.signaturePolicyService) {
SignaturePolicyIdentifierType signaturePolicyIdentifier = this.xadesObjectFactory
.createSignaturePolicyIdentifierType();
signedSignatureProperties.setSignaturePolicyIdentifier(signaturePolicyIdentifier);
SignaturePolicyIdType signaturePolicyId = this.xadesObjectFactory.createSignaturePolicyIdType();
signaturePolicyIdentifier.setSignaturePolicyId(signaturePolicyId);
ObjectIdentifierType objectIdentifier = this.xadesObjectFactory.createObjectIdentifierType();
signaturePolicyId.setSigPolicyId(objectIdentifier);
IdentifierType identifier = this.xadesObjectFactory.createIdentifierType();
objectIdentifier.setIdentifier(identifier);
identifier.setValue(this.signaturePolicyService.getSignaturePolicyIdentifier());
objectIdentifier.setDescription(this.signaturePolicyService.getSignaturePolicyDescription());
byte[] signaturePolicyDocumentData = this.signaturePolicyService.getSignaturePolicyDocument();
DigestAlgAndValueType sigPolicyHash = getDigestAlgAndValue(signaturePolicyDocumentData,
this.xadesObjectFactory, this.xmldsigObjectFactory, this.digestAlgorithm);
signaturePolicyId.setSigPolicyHash(sigPolicyHash);
String signaturePolicyDownloadUrl = this.signaturePolicyService.getSignaturePolicyDownloadUrl();
if (null != signaturePolicyDownloadUrl) {
SigPolicyQualifiersListType sigPolicyQualifiers = this.xadesObjectFactory
.createSigPolicyQualifiersListType();
signaturePolicyId.setSigPolicyQualifiers(sigPolicyQualifiers);
AnyType sigPolicyQualifier = this.xadesObjectFactory.createAnyType();
sigPolicyQualifiers.getSigPolicyQualifier().add(sigPolicyQualifier);
JAXBElement<String> spUriElement = this.xadesObjectFactory.createSPURI(signaturePolicyDownloadUrl);
sigPolicyQualifier.getContent().add(spUriElement);
}
} else if (this.signaturePolicyImplied) {
SignaturePolicyIdentifierType signaturePolicyIdentifier = this.xadesObjectFactory
.createSignaturePolicyIdentifierType();
signedSignatureProperties.setSignaturePolicyIdentifier(signaturePolicyIdentifier);
signaturePolicyIdentifier.setSignaturePolicyImplied("");
}
// DataObjectFormat
if (false == this.dataObjectFormatMimeTypes.isEmpty()) {
SignedDataObjectPropertiesType signedDataObjectProperties = this.xadesObjectFactory
.createSignedDataObjectPropertiesType();
signedProperties.setSignedDataObjectProperties(signedDataObjectProperties);
List<DataObjectFormatType> dataObjectFormats = signedDataObjectProperties.getDataObjectFormat();
for (Map.Entry<String, String> dataObjectFormatMimeType : this.dataObjectFormatMimeTypes.entrySet()) {
DataObjectFormatType dataObjectFormat = this.xadesObjectFactory.createDataObjectFormatType();
dataObjectFormat.setObjectReference("#" + dataObjectFormatMimeType.getKey());
dataObjectFormat.setMimeType(dataObjectFormatMimeType.getValue());
dataObjectFormats.add(dataObjectFormat);
}
}
// marshall XAdES QualifyingProperties
Node qualifyingPropertiesNode = marshallQualifyingProperties(document, this.xadesObjectFactory,
qualifyingProperties);
// add XAdES ds:Object
List<XMLStructure> xadesObjectContent = new LinkedList<XMLStructure>();
xadesObjectContent.add(new DOMStructure(qualifyingPropertiesNode));
XMLObject xadesObject = signatureFactory.newXMLObject(xadesObjectContent, null, null, null);
objects.add(xadesObject);
// add XAdES ds:Reference
DigestMethod digestMethod = signatureFactory.newDigestMethod(digestAlgorithm.getXmlAlgoId(), null);
List<Transform> transforms = new LinkedList<Transform>();
Transform exclusiveTransform = signatureFactory.newTransform(CanonicalizationMethod.INCLUSIVE,
(TransformParameterSpec) null);
transforms.add(exclusiveTransform);
Reference reference = signatureFactory.newReference("#" + signedPropertiesId, digestMethod, transforms,
XADES_TYPE, null);
references.add(reference);
}
private Node marshallQualifyingProperties(Document document, ObjectFactory xadesObjectFactory,
QualifyingPropertiesType qualifyingProperties) {
Node marshallNode = document.createElement("marshall-node");
try {
this.marshaller.marshal(xadesObjectFactory.createQualifyingProperties(qualifyingProperties), marshallNode);
} catch (JAXBException e) {
throw new RuntimeException("JAXB error: " + e.getMessage(), e);
}
Element qualifyingPropertiesElement = (Element) marshallNode.getFirstChild();
Element signedPropertiesElement = (Element) qualifyingPropertiesElement
.getElementsByTagNameNS("http://uri.etsi.org/01903/v1.3.2#", "SignedProperties").item(0);
signedPropertiesElement.setIdAttribute("Id", true);
return qualifyingPropertiesElement;
}
/**
* Gives back the JAXB DigestAlgAndValue data structure.
*
* @param data
* @param xadesObjectFactory
* @param xmldsigObjectFactory
* @param digestAlgorithm
* @return
*/
public static DigestAlgAndValueType getDigestAlgAndValue(byte[] data, ObjectFactory xadesObjectFactory,
be.fedict.eid.applet.service.signer.jaxb.xmldsig.ObjectFactory xmldsigObjectFactory,
DigestAlgo digestAlgorithm) {
DigestAlgAndValueType digestAlgAndValue = xadesObjectFactory.createDigestAlgAndValueType();
DigestMethodType digestMethod = xmldsigObjectFactory.createDigestMethodType();
digestAlgAndValue.setDigestMethod(digestMethod);
digestMethod.setAlgorithm(digestAlgorithm.getXmlAlgoId());
MessageDigest messageDigest;
try {
messageDigest = MessageDigest.getInstance(digestAlgorithm.getAlgoId());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("message digest algo error: " + e.getMessage(), e);
}
byte[] digestValue = messageDigest.digest(data);
digestAlgAndValue.setDigestValue(digestValue);
return digestAlgAndValue;
}
/**
* Gives back the JAXB CertID data structure.
*
* @param certificate
* @param xadesObjectFactory
* @param xmldsigObjectFactory
* @param digestAlgorithm
* @return
*/
public static CertIDType getCertID(X509Certificate certificate, ObjectFactory xadesObjectFactory,
be.fedict.eid.applet.service.signer.jaxb.xmldsig.ObjectFactory xmldsigObjectFactory,
DigestAlgo digestAlgorithm, boolean issuerNameNoReverseOrder) {
CertIDType certId = xadesObjectFactory.createCertIDType();
X509IssuerSerialType issuerSerial = xmldsigObjectFactory.createX509IssuerSerialType();
certId.setIssuerSerial(issuerSerial);
String issuerName;
if (issuerNameNoReverseOrder) {
try {
/*
* Make sure the DN is encoded using the same order as present
* within the certificate. This is an Office2010 work-around.
* Should be reverted back.
*
* XXX: not correct according to RFC 4514.
*/
issuerName = PrincipalUtil.getIssuerX509Principal(certificate).getName().replace(",", ", ");
} catch (CertificateEncodingException e) {
throw new RuntimeException("cert encoding error: " + e.getMessage(), e);
}
} else {
issuerName = certificate.getIssuerX500Principal().toString();
}
issuerSerial.setX509IssuerName(issuerName);
issuerSerial.setX509SerialNumber(certificate.getSerialNumber());
byte[] encodedCertificate;
try {
encodedCertificate = certificate.getEncoded();
} catch (CertificateEncodingException e) {
throw new RuntimeException("certificate encoding error: " + e.getMessage(), e);
}
DigestAlgAndValueType certDigest = getDigestAlgAndValue(encodedCertificate, xadesObjectFactory,
xmldsigObjectFactory, digestAlgorithm);
certId.setCertDigest(certDigest);
return certId;
}
/**
* Adds a mime-type for the given ds:Reference (referred via its @URI). This
* information is added via the xades:DataObjectFormat element.
*
* @param dsReferenceUri
* @param mimetype
*/
public void addMimeType(String dsReferenceUri, String mimetype) {
this.dataObjectFormatMimeTypes.put(dsReferenceUri, mimetype);
}
/**
* Sets the Id that will be used on the SignedProperties element;
*
* @param idSignedProperties
*/
public void setIdSignedProperties(String idSignedProperties) {
this.idSignedProperties = idSignedProperties;
}
/**
* Sets the signature policy to implied.
*
* @param signaturePolicyImplied
*/
public void setSignaturePolicyImplied(boolean signaturePolicyImplied) {
this.signaturePolicyImplied = signaturePolicyImplied;
}
/**
* Sets the XAdES XML namespace prefix.
*
* @param xadesNamespacePrefix
*/
public void setXadesNamespacePrefix(String xadesNamespacePrefix) {
this.xadesNamespacePrefixMapper.setXAdESNamespacePrefix(xadesNamespacePrefix);
}
/**
* Sets the XAdES claimed role.
*
* @param role
*/
public void setRole(String role) {
this.role = role;
}
/**
* Work-around for Office 2010 IssuerName encoding.
*
* @param reverseOrder
*/
public void setIssuerNameNoReverseOrder(boolean reverseOrder) {
this.issuerNameNoReverseOrder = reverseOrder;
}
}