/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.camel.component.xmlsecurity.api; import java.io.IOException; import java.io.StringReader; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Set; import java.util.TreeSet; import java.util.UUID; import javax.security.auth.x500.X500Principal; import javax.xml.crypto.dom.DOMStructure; 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.XMLSignature; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.apache.camel.Message; import org.apache.commons.codec.binary.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.apache.camel.util.ObjectHelper.isNotEmpty; /** * Implementation of the XAdES-BES and XAdES-EPES properties defined in * http://www.etsi.org/deliver/etsi_ts%5C101900_101999%5C101903%5C01.04 * .02_60%5Cts_101903v010402p.pdf. XAdES-T and XAdES-C is not implemented. * <p> * You have to overwrite the method {@link #getSigningCertificate()} or * {@link #getSigningCertificateChain()} if you want to have a * 'SigningCertificate' element in your XML Signature. * <p> * Further limitations: * <ul> * <li>No support for the 'QualifyingPropertiesReference' element (see section * 6.3.2 of spec).</li> * <li>No support for the 'Transforms' element contained in the * 'SignaturePolicyId' element contained in 'SignaturePolicyIdentifier' element</li> * <li>No support of the 'CounterSignature' element --> no support for the * 'UnsignedProperties' element</li> * <li>A 'CommitmentTypeIndication' element contains always the * 'AllSignedDataObjects' element. The 'ObjectReference' element within the * 'CommitmentTypeIndication' element is not supported.</li> * <li>The 'AllDataObjectsTimeStamp' element is not supported (it requires a * time authority)</li> * <li>The 'IndividualDataObjectsTimeStamp' element is not supported (it * requires a time authority)</li> * </ul> */ public class XAdESSignatureProperties implements XmlSignatureProperties { public static final String HTTP_URI_ETSI_ORG_01903_V1_3_2 = "http://uri.etsi.org/01903/v1.3.2#"; public static final String HTTP_URI_ETSI_ORG_01903_V1_1_1 = "http://uri.etsi.org/01903/v1.1.1#"; public static final String HTTP_URI_ETSI_ORG_01903_V1_2_2 = "http://uri.etsi.org/01903/v1.2.2#"; public static final String SIG_POLICY_NONE = "None"; public static final String SIG_POLICY_IMPLIED = "Implied"; public static final String SIG_POLICY_EXPLICIT_ID = "ExplicitId"; private static final Logger LOG = LoggerFactory.getLogger(XAdESSignatureProperties.class); private static final Set<String> SIG_POLICY_VALUES = new TreeSet<String>(); private boolean addSigningTime = true; private String namespace = HTTP_URI_ETSI_ORG_01903_V1_3_2; private String prefix = "etsi"; private List<String> signingCertificateURIs = Collections.emptyList(); private String digestAlgorithmForSigningCertificate = DigestMethod.SHA256; //"http://www.w3.org/2000/09/xmldsig#sha1"; private String signaturePolicy = SIG_POLICY_NONE; private String sigPolicyId; private String sigPolicyIdQualifier; private String sigPolicyIdDescription; private List<String> sigPolicyIdDocumentationReferences = Collections.emptyList(); private String signaturePolicyDigestAlgorithm = DigestMethod.SHA256; //"http://www.w3.org/2000/09/xmldsig#sha1"; private String signaturePolicyDigestValue; private List<String> sigPolicyQualifiers = Collections.emptyList(); private String dataObjectFormatDescription; private String dataObjectFormatMimeType; private String dataObjectFormatIdentifier; private String dataObjectFormatIdentifierQualifier; private String dataObjectFormatIdentifierDescription; private List<String> dataObjectFormatIdentifierDocumentationReferences = Collections.emptyList(); private List<String> signerClaimedRoles = Collections.emptyList(); private List<XAdESEncapsulatedPKIData> signerCertifiedRoles = Collections.emptyList(); private String signatureProductionPlaceCity; private String signatureProductionPlaceStateOrProvince; private String signatureProductionPlacePostalCode; private String signatureProductionPlaceCountryName; private String commitmentTypeId; private String commitmentTypeIdQualifier; private String commitmentTypeIdDescription; private List<String> commitmentTypeIdDocumentationReferences = Collections.emptyList(); private List<String> commitmentTypeQualifiers = Collections.emptyList(); static { SIG_POLICY_VALUES.add(SIG_POLICY_NONE); SIG_POLICY_VALUES.add(SIG_POLICY_IMPLIED); SIG_POLICY_VALUES.add(SIG_POLICY_EXPLICIT_ID); } public XAdESSignatureProperties() { } public boolean isAddSigningTime() { return addSigningTime; } public void setAddSigningTime(boolean addSigningTime) { this.addSigningTime = addSigningTime; } public String getNamespace() { return namespace; } public void setNamespace(String namespace) { if (namespace == null) { throw new IllegalArgumentException("Parameter 'namespace' is null"); } this.namespace = namespace; } protected String findNamespace(Message message) { return message.getHeader(XmlSignatureConstants.HEADER_XADES_NAMESPACE, getNamespace(), String.class); } public String getPrefix() { return prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } protected String findPrefix(Message message) { return message.getHeader(XmlSignatureConstants.HEADER_XADES_PREFIX, getPrefix(), String.class); } /** * URIs of the signing certificate or signing certificate chain. For the * sining certificate the first URI is taken. If there is a signing * certificate chain specified, then the URIs are assigned to the * certificates in the chain in the order given in the provided list. You * have to specify an empty entry (null or empty srting), if no URI should * be assigned to a specific certificate in the list. If you specify an * empty list, then no URIs are assigned. * * @throws IllegalArgumentException * if the parameter is <code>null</code> or one of the URIs is * <code>null</code> */ public void setSigningCertificateURIs(List<String> signingCertificateURIs) { if (signingCertificateURIs == null) { throw new IllegalArgumentException("Parameter 'signingCertificateURIs' is null"); } this.signingCertificateURIs = new ArrayList<String>(signingCertificateURIs); } public List<String> getSigningCertificateURIs() { return signingCertificateURIs; } public String getDigestAlgorithmForSigningCertificate() { return digestAlgorithmForSigningCertificate; } /** * Digest Algorithm for creating the digest of the signing certificate. * Possible values: "http://www.w3.org/2000/09/xmldsig#sha1", * "http://www.w3.org/2001/04/xmlenc#sha256", * "http://www.w3.org/2001/04/xmldsig-more#sha384", * "http://www.w3.org/2001/04/xmlenc#sha512". Default value is * "http://www.w3.org/2001/04/xmlenc#sha256". * */ public void setDigestAlgorithmForSigningCertificate(String digestAlgorithm) { this.digestAlgorithmForSigningCertificate = digestAlgorithm; } public String getSignaturePolicy() { return signaturePolicy; } /** * Signature Policy. Possible values: {@link #SIG_POLICY_NONE}, * {@link #SIG_POLICY_IMPLIED}, {@link #SIG_POLICY_EXPLICIT_ID}. Default * value is {@link #SIG_POLICY_NONE}. * */ public void setSignaturePolicy(String signaturePolicy) { if (!SIG_POLICY_VALUES.contains(signaturePolicy)) { throw new IllegalArgumentException(String.format( "Signature policy '%s' is invalid. Possible values are 'None', 'Implied', and 'ExplicitId'.", signaturePolicy)); } this.signaturePolicy = signaturePolicy; } public String getSigPolicyId() { return sigPolicyId; } /** * Identifier must be specified if {@link #getSignaturePolicy()} equals * "ExplicitId". Must be an URI */ public void setSigPolicyId(String sigPolicyId) { this.sigPolicyId = sigPolicyId; } public String getSigPolicyIdQualifier() { return sigPolicyIdQualifier; } /** * Qualifier for the Signature Policy Identifier. Possible values are * <code>null</code> (which means no Qualifier element is created), * "OIDAsURI", or "OIDAsURN". Default value is <code>null</code>. If the * identifier is an OID then a qualifier must be set. */ public void setSigPolicyIdQualifier(String sigPolicyIdQualifier) { this.sigPolicyIdQualifier = sigPolicyIdQualifier; } public String getSigPolicyIdDescription() { return sigPolicyIdDescription; } public void setSigPolicyIdDescription(String sigPolicyIdDescription) { this.sigPolicyIdDescription = sigPolicyIdDescription; } public List<String> getSigPolicyIdDocumentationReferences() { return sigPolicyIdDocumentationReferences; } /** * * Sets the documentation references of the signature policy. * * @throws IllegalArgumentException * if the parameter is <code>null</code> or one of the * documentation references is <code>null</code> or empty */ public void setSigPolicyIdDocumentationReferences(List<String> sigPolicyIdDocumentationReferences) { if (sigPolicyIdDocumentationReferences == null) { throw new IllegalArgumentException("Parameter 'sigPolicyIdDocumentationReferences' is null"); } for (String ref : sigPolicyIdDocumentationReferences) { if (ref == null || ref.isEmpty()) { throw new IllegalArgumentException("At least one documentation reference of the signature policy is null or empty"); } } this.sigPolicyIdDocumentationReferences = sigPolicyIdDocumentationReferences; } public String getSignaturePolicyDigestAlgorithm() { return signaturePolicyDigestAlgorithm; } /** * Digest Algorithm for creating the digest of the signature policy * document. Possible values: "http://www.w3.org/2000/09/xmldsig#sha1", * "http://www.w3.org/2001/04/xmlenc#sha256", * "http://www.w3.org/2001/04/xmldsig-more#sha384", * "http://www.w3.org/2001/04/xmlenc#sha512". Default value is * "http://www.w3.org/2001/04/xmlenc#sha256". * */ public void setSignaturePolicyDigestAlgorithm(String signaturePolicyDigestAlgorithm) { this.signaturePolicyDigestAlgorithm = signaturePolicyDigestAlgorithm; } public String getSignaturePolicyDigestValue() { return signaturePolicyDigestValue; } /** Digest value for the signature policy base 64 encoded. */ public void setSignaturePolicyDigestValue(String signaturePolicyDigestValue) { this.signaturePolicyDigestValue = signaturePolicyDigestValue; } public List<String> getSigPolicyQualifiers() { return sigPolicyQualifiers; } /** * Sets the signature policy qualifiers. Each qualifier can be a text or a * XML fragment with the root element 'SigPolicyQualifier' with the XAdES * namespace. * * @throws IllegalArgumentException * if the input parameter is <code>null</code>, or one of the * qualifiers is <code>null</code> or empty * */ public void setSigPolicyQualifiers(List<String> sigPolicyQualifiers) { if (sigPolicyQualifiers == null) { throw new IllegalArgumentException("Parameter 'sigPolicyQualifiers' is null"); } for (String qualifier : sigPolicyQualifiers) { if (qualifier == null || qualifier.isEmpty()) { throw new IllegalArgumentException("At least one of the policy qualifiers is null or empty"); } } this.sigPolicyQualifiers = new ArrayList<String>(sigPolicyQualifiers); } public String getDataObjectFormatDescription() { return dataObjectFormatDescription; } public void setDataObjectFormatDescription(String dataObjectFormatDescription) { this.dataObjectFormatDescription = dataObjectFormatDescription; } public String getDataObjectFormatMimeType() { return dataObjectFormatMimeType; } public void setDataObjectFormatMimeType(String dataObjectFormatMimeType) { this.dataObjectFormatMimeType = dataObjectFormatMimeType; } public String getDataObjectFormatIdentifier() { return dataObjectFormatIdentifier; } public void setDataObjectFormatIdentifier(String dataObjectFormatIdentifier) { this.dataObjectFormatIdentifier = dataObjectFormatIdentifier; } public String getDataObjectFormatIdentifierQualifier() { return dataObjectFormatIdentifierQualifier; } /** * Qualifier for the Format Identifier. Possible values are * <code>null</code> (which means no Qualifier element is created), * "OIDAsURI", or "OIDAsURN". Default value is <code>null</code>. If the * identifier is an OID then a qualifier must be set. */ public void setDataObjectFormatIdentifierQualifier(String dataObjectFormatIdentifierQualifier) { this.dataObjectFormatIdentifierQualifier = dataObjectFormatIdentifierQualifier; } public String getDataObjectFormatIdentifierDescription() { return dataObjectFormatIdentifierDescription; } public void setDataObjectFormatIdentifierDescription(String dataObjectFormatIdentifierDescription) { this.dataObjectFormatIdentifierDescription = dataObjectFormatIdentifierDescription; } public List<String> getDataObjectFormatIdentifierDocumentationReferences() { return dataObjectFormatIdentifierDocumentationReferences; } /** * * Sets the documentation references of the data object format identifier. * * @throws IllegalArgumentException * if the parameter is <code>null</code> or one of the * documentation references is <code>null</code> or empty */ public void setDataObjectFormatIdentifierDocumentationReferences(List<String> dataObjectFormatIdentifierDocumentationReferences) { if (dataObjectFormatIdentifierDocumentationReferences == null) { throw new IllegalArgumentException("Parameter 'dataObjectFormatIdentifierDocumentationReferences' is null"); } for (String ref : dataObjectFormatIdentifierDocumentationReferences) { if (ref == null || ref.isEmpty()) { throw new IllegalArgumentException("At least one reference of the identifier of the data object format is null or empty"); } } this.dataObjectFormatIdentifierDocumentationReferences = new ArrayList<String>(dataObjectFormatIdentifierDocumentationReferences); } public List<String> getSignerClaimedRoles() { return signerClaimedRoles; } /** * Sets the claimed roles list. A role can be either a text or a XML * fragment with the root element 'ClaimedRole' with the XAdES namespace. * * @throws IllegalArgumentException * if <tt>signerClaimedRoles</tt> is <code>null</code>, or if * one of the roles is <code>null</code> or empty */ public void setSignerClaimedRoles(List<String> signerClaimedRoles) { if (signerClaimedRoles == null) { throw new IllegalArgumentException("Parameter 'signerClaimedRoles' is null"); } for (String role : signerClaimedRoles) { if (role == null || role.isEmpty()) { throw new IllegalArgumentException("At least one of the signer claimed roles is null or empty"); } } this.signerClaimedRoles = new ArrayList<String>(signerClaimedRoles); } public List<XAdESEncapsulatedPKIData> getSignerCertifiedRoles() { return signerCertifiedRoles; } /** * Sets the certified roles. * * @throws IllegalArgumentException * if <tt>signerCertifiedRoles</tt> is <code>null</code> */ public void setSignerCertifiedRoles(List<XAdESEncapsulatedPKIData> signerCertifiedRoles) { if (signerCertifiedRoles == null) { throw new IllegalArgumentException("Parameter 'signerCertifiedRoles' is null"); } for (XAdESEncapsulatedPKIData role : signerCertifiedRoles) { if (role == null) { throw new IllegalArgumentException("At least one of the signer certified roles is null"); } } this.signerCertifiedRoles = new ArrayList<XAdESEncapsulatedPKIData>(signerCertifiedRoles); } public String getSignatureProductionPlaceCity() { return signatureProductionPlaceCity; } public void setSignatureProductionPlaceCity(String signatureProductionPlaceCity) { this.signatureProductionPlaceCity = signatureProductionPlaceCity; } public String getSignatureProductionPlaceStateOrProvince() { return signatureProductionPlaceStateOrProvince; } public void setSignatureProductionPlaceStateOrProvince(String signatureProductionPlaceStateOrProvince) { this.signatureProductionPlaceStateOrProvince = signatureProductionPlaceStateOrProvince; } public String getSignatureProductionPlacePostalCode() { return signatureProductionPlacePostalCode; } public void setSignatureProductionPlacePostalCode(String signatureProductionPlacePostalCode) { this.signatureProductionPlacePostalCode = signatureProductionPlacePostalCode; } public String getSignatureProductionPlaceCountryName() { return signatureProductionPlaceCountryName; } public void setSignatureProductionPlaceCountryName(String signatureProductionPlaceCountryName) { this.signatureProductionPlaceCountryName = signatureProductionPlaceCountryName; } public String getCommitmentTypeId() { return commitmentTypeId; } public void setCommitmentTypeId(String commitmentTypeId) { this.commitmentTypeId = commitmentTypeId; } public String getCommitmentTypeIdQualifier() { return commitmentTypeIdQualifier; } /** * Qualifier for the Commitment Type ID. Possible values are * <code>null</code> (which means no Qualifier element is created), * "OIDAsURI", or "OIDAsURN". Default value is <code>null</code>. If the * identifier is an OID then a qualifier must be set. */ public void setCommitmentTypeIdQualifier(String commitmentTypeIdQualifier) { this.commitmentTypeIdQualifier = commitmentTypeIdQualifier; } public String getCommitmentTypeIdDescription() { return commitmentTypeIdDescription; } public void setCommitmentTypeIdDescription(String commitmentTypeIdDescription) { this.commitmentTypeIdDescription = commitmentTypeIdDescription; } public List<String> getCommitmentTypeIdDocumentationReferences() { return commitmentTypeIdDocumentationReferences; } /** * Sets the documentation references for the Commitment Type ID: * * @throws IllegalArgumentException * if the parameter is <code>null</code> or a documentation * reference is <code>null</code> or empty * */ public void setCommitmentTypeIdDocumentationReferences(List<String> commitmentTypeIdDocumentationReferences) { if (commitmentTypeIdDocumentationReferences == null) { throw new IllegalArgumentException("Parameter 'commitmentTypeIdDocumentationReferences' is null"); } for (String ref : commitmentTypeIdDocumentationReferences) { if (ref == null || ref.isEmpty()) { throw new IllegalArgumentException("At least one documentation reference of the commitment type is null or empty"); } } this.commitmentTypeIdDocumentationReferences = new ArrayList<String>(commitmentTypeIdDocumentationReferences); } public List<String> getCommitmentTypeQualifiers() { return commitmentTypeQualifiers; } /** * List of additional qualifying information on the commitment. Each list * element can be a text or an XML fragment with the root element * 'CommitmentTypeQualifier' with the XAdES namespace. * * @throws IllegalArgumentException * if the input parameter is <code>null</code>, or one qualifier * is <code>null</code> or empty */ public void setCommitmentTypeQualifiers(List<String> commitmentTypeQualifiers) { if (commitmentTypeQualifiers == null) { throw new IllegalArgumentException("Parameter 'commitmentTypeQualifiers' is null"); } for (String qualifier : commitmentTypeQualifiers) { if (qualifier == null || qualifier.isEmpty()) { throw new IllegalArgumentException("At least one qualifier of the commitment type is null or empty"); } } this.commitmentTypeQualifiers = new ArrayList<String>(commitmentTypeQualifiers); } @Override public Output get(Input input) throws Exception { //NOPMD XmlSignatureProperties.Output result = new Output(); if (!isAddSignedSignatureProperties() && !isAddSignedDataObjectPropeties()) { LOG.debug("XAdES signature properties are empty. Therefore no XAdES element will be added to the signature."); return result; } String signedPropertiesId = "_" + UUID.randomUUID().toString(); List<Transform> transforms = Collections.emptyList(); Reference ref = input.getSignatureFactory().newReference("#" + signedPropertiesId, input.getSignatureFactory().newDigestMethod(input.getContentDigestAlgorithm(), null), transforms, "http://uri.etsi.org/01903#SignedProperties", null); Node parent = input.getParent(); Document doc; if (Node.DOCUMENT_NODE == parent.getNodeType()) { doc = (Document) parent; // enveloping } else { doc = parent.getOwnerDocument(); // enveloped } Element qualifyingProperties = createElement("QualifyingProperties", doc, input); setIdAttributeFromHeader(XmlSignatureConstants.HEADER_XADES_QUALIFYING_PROPERTIES_ID, qualifyingProperties, input); String signatureId = input.getSignatureId(); if (signatureId == null || signatureId.isEmpty()) { LOG.debug("No signature Id configured. Therefore a value is generated."); // generate one signatureId = "_" + UUID.randomUUID().toString(); // and set to output result.setSignatureId(signatureId); } setAttribute(qualifyingProperties, "Target", "#" + signatureId); Element signedProperties = createElement("SignedProperties", doc, input); qualifyingProperties.appendChild(signedProperties); setAttribute(signedProperties, "Id", signedPropertiesId); signedProperties.setIdAttribute("Id", true); addSignedSignatureProperties(doc, signedProperties, input); String contentReferenceId = addSignedDataObjectProperties(doc, signedProperties, input); result.setContentReferenceId(contentReferenceId); DOMStructure structure = new DOMStructure(qualifyingProperties); XMLObject propertiesObject = input.getSignatureFactory().newXMLObject(Collections.singletonList(structure), null, null, null); result.setReferences(Collections.singletonList(ref)); result.setObjects(Collections.singletonList(propertiesObject)); return result; } protected void setAttribute(Element element, String attrName, String value) { // element.setAttribute(name, value); did cause NullPointerException in santuario 2.02 element.setAttributeNS("", attrName, value); } protected void setIdAttributeFromHeader(String header, Element element, Input input) { String value = input.getMessage().getHeader(header, String.class); if (value != null && !value.isEmpty()) { setAttribute(element, "Id", value); element.setIdAttribute("Id", true); } } protected String addSignedDataObjectProperties(Document doc, Element signedProperties, Input input) throws XmlSignatureException, SAXException, IOException, ParserConfigurationException { if (isAddSignedDataObjectPropeties()) { Element signedDataObjectProperties = createElement("SignedDataObjectProperties", doc, input); setIdAttributeFromHeader(XmlSignatureConstants.HEADER_XADES_SIGNED_DATA_OBJECT_PROPERTIES_ID, signedDataObjectProperties, input); signedProperties.appendChild(signedDataObjectProperties); String contentReferenceId = addDataObjectFormat(signedDataObjectProperties, doc, input); addCommitmentTypeIndication(signedDataObjectProperties, doc, input); return contentReferenceId; } else { return null; } } protected boolean isAddSignedDataObjectPropeties() { return isAddDataObjectFormat() || isAddCommitmentType(); } protected void addCommitmentTypeIndication(Element signedDataObjectProperties, Document doc, Input input) throws SAXException, IOException, ParserConfigurationException, XmlSignatureException { if (!isAddCommitmentType()) { return; } Element commitmentTypeIndication = createElement("CommitmentTypeIndication", doc, input); signedDataObjectProperties.appendChild(commitmentTypeIndication); Element commitmentTypeIdEl = createElement("CommitmentTypeId", doc, input); commitmentTypeIndication.appendChild(commitmentTypeIdEl); Element identifier = createElement("Identifier", doc, input); commitmentTypeIdEl.appendChild(identifier); identifier.setTextContent(getCommitmentTypeId()); if (getCommitmentTypeIdQualifier() != null && !getCommitmentTypeIdQualifier().isEmpty()) { setAttribute(identifier, "Qualifier", getCommitmentTypeIdQualifier()); } if (getCommitmentTypeIdDescription() != null && !getCommitmentTypeIdDescription().isEmpty()) { Element description = createElement("Description", doc, input); commitmentTypeIdEl.appendChild(description); description.setTextContent(getCommitmentTypeIdDescription()); } if (!getCommitmentTypeIdDocumentationReferences().isEmpty()) { Element documentationReferences = createElement("DocumentationReferences", doc, input); commitmentTypeIdEl.appendChild(documentationReferences); List<String> docReferences = getCommitmentTypeIdDocumentationReferences(); for (String documentationReferenceValue : docReferences) { Element documentationReference = createElement("DocumentationReference", doc, input); documentationReferences.appendChild(documentationReference); documentationReference.setTextContent(documentationReferenceValue); } } Element allSignedDataObjects = createElement("AllSignedDataObjects", doc, input); commitmentTypeIndication.appendChild(allSignedDataObjects); List<String> qualifiers = getCommitmentTypeQualifiers(); if (!qualifiers.isEmpty()) { Element qualifiersEl = createElement("CommitmentTypeQualifiers", doc, input); commitmentTypeIndication.appendChild(qualifiersEl); String errorMessage = "The XAdES confguration is invalid. The list of the commitment type qualifiers contains the invalid entry '%s'. An entry must either be a text or an XML fragment " + "with the root element '%s' with the namespace '%s'."; for (String qualifier : getCommitmentTypeQualifiers()) { Element qualifierEl = createChildFromXmlFragmentOrText(doc, input, "CommitmentTypeQualifier", errorMessage, qualifier); qualifiersEl.appendChild(qualifierEl); } } } protected boolean isAddCommitmentType() { return getCommitmentTypeId() != null && !getCommitmentTypeId().isEmpty(); } protected String addDataObjectFormat(Element signedDataObjectProperties, Document doc, Input input) throws XmlSignatureException { if (!isAddDataObjectFormat()) { return null; } Element dataObjectFormat = createElement("DataObjectFormat", doc, input); signedDataObjectProperties.appendChild(dataObjectFormat); String contentReferenceId = "_" + UUID.randomUUID().toString(); setAttribute(dataObjectFormat, "ObjectReference", contentReferenceId); if (getDataObjectFormatDescription() != null && !getDataObjectFormatDescription().isEmpty()) { Element description = createElement("Description", doc, input); dataObjectFormat.appendChild(description); description.setTextContent(getDataObjectFormatDescription()); } if (getDataObjectFormatIdentifier() != null && !getDataObjectFormatIdentifier().isEmpty()) { Element objectIdentifier = createElement("ObjectIdentifier", doc, input); dataObjectFormat.appendChild(objectIdentifier); Element identifier = createElement("Identifier", doc, input); objectIdentifier.appendChild(identifier); identifier.setTextContent(getDataObjectFormatIdentifier()); if (getDataObjectFormatIdentifierQualifier() != null && !getDataObjectFormatIdentifierQualifier().isEmpty()) { setAttribute(identifier, "Qualifier", getDataObjectFormatIdentifierQualifier()); } if (getDataObjectFormatIdentifierDescription() != null && !getDataObjectFormatIdentifierDescription().isEmpty()) { Element description = createElement("Description", doc, input); objectIdentifier.appendChild(description); description.setTextContent(getDataObjectFormatIdentifierDescription()); } if (!getDataObjectFormatIdentifierDocumentationReferences().isEmpty()) { Element documentationReferences = createElement("DocumentationReferences", doc, input); objectIdentifier.appendChild(documentationReferences); List<String> docReferences = getDataObjectFormatIdentifierDocumentationReferences(); for (String documentationReferenceValue : docReferences) { Element documentationReference = createElement("DocumentationReference", doc, input); documentationReferences.appendChild(documentationReference); documentationReference.setTextContent(documentationReferenceValue); } } } if (getDataObjectFormatMimeType() != null && !getDataObjectFormatMimeType().isEmpty()) { Element mimeType = createElement("MimeType", doc, input); dataObjectFormat.appendChild(mimeType); mimeType.setTextContent(getDataObjectFormatMimeType()); } String encoding = input.getMessage().getHeader(XmlSignatureConstants.HEADER_XADES_DATA_OBJECT_FORMAT_ENCODING, String.class); if (encoding != null && !encoding.isEmpty()) { Element encodingEl = createElement("Encoding", doc, input); dataObjectFormat.appendChild(encodingEl); encodingEl.setTextContent(encoding); } return contentReferenceId; } protected boolean isAddDataObjectFormat() { return (getDataObjectFormatIdentifier() != null && !getDataObjectFormatIdentifier().isEmpty()) || (getDataObjectFormatDescription() != null && !getDataObjectFormatDescription().isEmpty()) || (getDataObjectFormatMimeType() != null && !getDataObjectFormatMimeType().isEmpty()); } protected void addSignedSignatureProperties(Document doc, Element signedProperties, Input input) throws Exception { //NOPMD if (isAddSignedSignatureProperties()) { LOG.debug("Adding signed signature properties"); Element signedSignatureProperties = createElement("SignedSignatureProperties", doc, input); setIdAttributeFromHeader(XmlSignatureConstants.HEADER_XADES_SIGNED_SIGNATURE_PROPERTIES_ID, signedSignatureProperties, input); signedProperties.appendChild(signedSignatureProperties); addSigningTime(doc, signedSignatureProperties, input); addSigningCertificate(doc, signedSignatureProperties, input); addSignaturePolicyIdentifier(doc, signedSignatureProperties, input); addSignatureProductionPlace(doc, signedSignatureProperties, input); addSignerRole(doc, signedSignatureProperties, input); } } protected boolean isAddSignedSignatureProperties() throws Exception { //NOPMD return isAddSigningTime() || getSigningCertificate() != null || (getSigningCertificateChain() != null && getSigningCertificateChain().length > 0) || isAddSignaturePolicy() || isAddSignatureProductionPlace() || isAddSignerRole(); } protected boolean isAddSignerRole() { return getSignerClaimedRoles().size() > 0 || getSignerCertifiedRoles().size() > 0; } protected void addSignatureProductionPlace(Document doc, Element signedSignatureProperties, Input input) { if (!isAddSignatureProductionPlace()) { return; } Element signatureProductionPlace = createElement("SignatureProductionPlace", doc, input); signedSignatureProperties.appendChild(signatureProductionPlace); if (getSignatureProductionPlaceCity() != null && !getSignatureProductionPlaceCity().isEmpty()) { LOG.debug("Adding production city"); Element city = createElement("City", doc, input); signatureProductionPlace.appendChild(city); city.setTextContent(getSignatureProductionPlaceCity()); } if (getSignatureProductionPlaceStateOrProvince() != null && !getSignatureProductionPlaceStateOrProvince().isEmpty()) { LOG.debug("Adding production state or province"); Element stateOrProvince = createElement("StateOrProvince", doc, input); signatureProductionPlace.appendChild(stateOrProvince); stateOrProvince.setTextContent(getSignatureProductionPlaceStateOrProvince()); } if (getSignatureProductionPlacePostalCode() != null && !getSignatureProductionPlacePostalCode().isEmpty()) { LOG.debug("Adding production postal code"); Element postalCode = createElement("PostalCode", doc, input); signatureProductionPlace.appendChild(postalCode); postalCode.setTextContent(getSignatureProductionPlacePostalCode()); } if (getSignatureProductionPlaceCountryName() != null && !getSignatureProductionPlaceCountryName().isEmpty()) { LOG.debug("Adding production country name"); Element countryName = createElement("CountryName", doc, input); signatureProductionPlace.appendChild(countryName); countryName.setTextContent(getSignatureProductionPlaceCountryName()); } } protected boolean isAddSignatureProductionPlace() { return isNotEmpty(getSignatureProductionPlaceCity()) || isNotEmpty(getSignatureProductionPlaceCountryName()) || isNotEmpty(getSignatureProductionPlacePostalCode()) || isNotEmpty(getSignatureProductionPlaceStateOrProvince()); } protected void addSignerRole(Document doc, Element signedSignatureProperties, Input input) throws XmlSignatureException, SAXException, IOException, ParserConfigurationException { if (!isAddSignerRole()) { return; } Element signerRole = createElement("SignerRole", doc, input); signedSignatureProperties.appendChild(signerRole); List<String> claimedRoles = getSignerClaimedRoles(); if (!claimedRoles.isEmpty()) { LOG.debug("Adding claimed roles"); Element claimedRolesEl = createElement("ClaimedRoles", doc, input); signerRole.appendChild(claimedRolesEl); String errorMessage = "The XAdES confguration is invalid. The list of the claimed roles contains the invalid entry '%s'." + " An entry must either be a text or an XML fragment with the root element '%s' with the namespace '%s'."; for (String claimedRole : claimedRoles) { Element claimedRoleEl = createChildFromXmlFragmentOrText(doc, input, "ClaimedRole", errorMessage, claimedRole); claimedRolesEl.appendChild(claimedRoleEl); } } List<XAdESEncapsulatedPKIData> certifiedRoles = getSignerCertifiedRoles(); if (!certifiedRoles.isEmpty()) { LOG.debug("Adding certified roles"); Element certifiedRolesEl = createElement("CertifiedRoles", doc, input); signerRole.appendChild(certifiedRolesEl); for (XAdESEncapsulatedPKIData certifiedRole : certifiedRoles) { Element certifiedRoleEl = createElement("CertifiedRole", doc, input); certifiedRolesEl.appendChild(certifiedRoleEl); certifiedRoleEl.setTextContent(certifiedRole.getBase64Conent()); if (certifiedRole.getEncoding() != null && !certifiedRole.getEncoding().isEmpty()) { setAttribute(certifiedRoleEl, "Encoding", certifiedRole.getEncoding()); } if (certifiedRole.getId() != null && !certifiedRole.getId().isEmpty()) { setAttribute(certifiedRoleEl, "Id", certifiedRole.getId()); certifiedRoleEl.setIdAttribute("Id", true); } } } } protected void addSignaturePolicyIdentifier(Document doc, Element signedProperties, Input input) throws XmlSignatureException, SAXException, IOException, ParserConfigurationException { if (!isAddSignaturePolicy()) { return; } Element signaturePolicyIdentifier = createElement("SignaturePolicyIdentifier", doc, input); signedProperties.appendChild(signaturePolicyIdentifier); if (SIG_POLICY_IMPLIED.equals(getSignaturePolicy())) { LOG.debug("Adding implied signature policy"); Element implied = createElement("SignaturePolicyImplied", doc, input); signaturePolicyIdentifier.appendChild(implied); } else if (SIG_POLICY_EXPLICIT_ID.equals(getSignaturePolicy())) { LOG.debug("Adding signatue policy ID"); Element id = createElement("SignaturePolicyId", doc, input); signaturePolicyIdentifier.appendChild(id); Element sigPolicyId = createElement("SigPolicyId", doc, input); id.appendChild(sigPolicyId); Element identifier = createElement("Identifier", doc, input); sigPolicyId.appendChild(identifier); if (getSigPolicyId() == null || getSigPolicyId().isEmpty()) { throw new XmlSignatureException("The XAdES-EPES confguration is invalid. The signature policy identifier is missing."); } identifier.setTextContent(getSigPolicyId()); if (getSigPolicyIdQualifier() != null && !getSigPolicyIdQualifier().isEmpty()) { setAttribute(identifier, "Qualifier", getSigPolicyIdQualifier()); } if (getSigPolicyIdDescription() != null && !getSigPolicyIdDescription().isEmpty()) { Element description = createElement("Description", doc, input); sigPolicyId.appendChild(description); description.setTextContent(getSigPolicyIdDescription()); } if (!getSigPolicyIdDocumentationReferences().isEmpty()) { Element documentationReferences = createElement("DocumentationReferences", doc, input); sigPolicyId.appendChild(documentationReferences); List<String> docReferences = getSigPolicyIdDocumentationReferences(); for (String documentationReferenceValue : docReferences) { Element documentationReference = createElement("DocumentationReference", doc, input); documentationReferences.appendChild(documentationReference); documentationReference.setTextContent(documentationReferenceValue); } } //here we could introduce the transformations for the signature policy, which we do not yet support Element sigPolicyHash = createElement("SigPolicyHash", doc, input); id.appendChild(sigPolicyHash); if (getSignaturePolicyDigestAlgorithm() == null || getSignaturePolicyDigestAlgorithm().isEmpty()) { throw new XmlSignatureException( "The XAdES-EPES confguration is invalid. The digest algorithm for the signature policy is missing."); } Element digestMethod = createDigSigElement("DigestMethod", doc, input.getPrefixForXmlSignatureNamespace()); sigPolicyHash.appendChild(digestMethod); setAttribute(digestMethod, "Algorithm", getSignaturePolicyDigestAlgorithm()); if (getSignaturePolicyDigestValue() == null || getSignaturePolicyDigestValue().isEmpty()) { throw new XmlSignatureException( "The XAdES-EPES confguration is invalid. The digest value for the signature policy is missing."); } Element digestValue = createDigSigElement("DigestValue", doc, input.getPrefixForXmlSignatureNamespace()); sigPolicyHash.appendChild(digestValue); digestValue.setTextContent(getSignaturePolicyDigestValue()); List<String> qualifiers = getSigPolicyQualifiers(); if (!qualifiers.isEmpty()) { Element qualifiersEl = createElement("SigPolicyQualifiers", doc, input); id.appendChild(qualifiersEl); String errorMessage = "The XAdES confguration is invalid. The list of the signatue policy qualifiers contains the invalid entry '%s'." + " An entry must either be a text or an XML fragment with the root element '%s' with the namespace '%s'."; for (String elementOrText : getSigPolicyQualifiers()) { Element child = createChildFromXmlFragmentOrText(doc, input, "SigPolicyQualifier", errorMessage, elementOrText); qualifiersEl.appendChild(child); } } } else { // cannot happen throw new IllegalStateException(String.format( "Invalid value '%s' for parameter 'SignaturePolicy'. Possible values are: 'None', 'Implied', and 'ExplictId'.", getSignaturePolicy())); } } protected Element createChildFromXmlFragmentOrText(Document doc, Input input, String localElementName, String errorMessage, String elementOrText) throws IOException, ParserConfigurationException, XmlSignatureException { String ending = localElementName + ">"; Element child; if (elementOrText.startsWith("<") && elementOrText.endsWith(ending)) { try { // assume xml InputSource source = new InputSource(new StringReader(elementOrText)); source.setEncoding("UTF-8"); Document parsedDoc = XmlSignatureHelper.newDocumentBuilder(Boolean.TRUE).parse(source); replacePrefixes(parsedDoc, input); child = (Element) doc.adoptNode(parsedDoc.getDocumentElement()); // check for correct namespace String ns = findNamespace(input.getMessage()); if (!ns.equals(child.getNamespaceURI())) { throw new XmlSignatureException( String.format( "The XAdES confguration is invalid. The root element '%s' of the provided XML fragment '%s' has the invalid namespace '%s'. The correct namespace is '%s'.", child.getLocalName(), elementOrText, child.getNamespaceURI(), ns)); } } catch (SAXException e) { throw new XmlSignatureException(String.format(errorMessage, elementOrText, localElementName, namespace), e); } } else { child = createElement(localElementName, doc, input); child.setTextContent(elementOrText); } return child; } protected void replacePrefixes(Document qualifierDoc, Input input) { Element el = qualifierDoc.getDocumentElement(); replacePrefix(el, input); List<Element> childElements = getChildElements(el); List<Element> collectedNewChildElements = new ArrayList<Element>(); for (; !childElements.isEmpty();) { collectedNewChildElements.clear(); for (Element child : childElements) { replacePrefix(child, input); List<Element> newChildElements = getChildElements(child); collectedNewChildElements.addAll(newChildElements); } childElements = new ArrayList<Element>(collectedNewChildElements); } } protected List<Element> getChildElements(Element el) { List<Element> childElements = new ArrayList<Element>(5); NodeList children = el.getChildNodes(); int length = children.getLength(); for (int i = 0; i < length; i++) { Node child = children.item(i); if (Node.ELEMENT_NODE == child.getNodeType()) { childElements.add((Element) child); } } return childElements; } protected void replacePrefix(Element el, Input input) { replacePrefixForNode(el, input); NamedNodeMap nnm = el.getAttributes(); List<Attr> xmlnsToBeRemoved = new ArrayList<Attr>(2); int length = nnm.getLength(); for (int i = 0; i < length; i++) { Node attr = nnm.item(i); replacePrefixForNode(attr, input); if (attr.getNodeType() == Node.ATTRIBUTE_NODE) { if ("xmlns".equals(attr.getLocalName()) || "xmlns".equals(attr.getPrefix())) { if (XMLSignature.XMLNS.equals(attr.getTextContent()) || findNamespace(input.getMessage()).equals(attr.getTextContent())) { xmlnsToBeRemoved.add((Attr) attr); } } } } // remove xml namespace declaration for XML signature and XAdES namespace for (Attr toBeRemoved : xmlnsToBeRemoved) { el.removeAttributeNode(toBeRemoved); } } protected void replacePrefixForNode(Node node, Input input) { if (XMLSignature.XMLNS.equals(node.getNamespaceURI())) { node.setPrefix(input.getPrefixForXmlSignatureNamespace()); } else if (findNamespace(input.getMessage()).equals(node.getNamespaceURI())) { node.setPrefix(findPrefix(input.getMessage())); } } protected boolean isAddSignaturePolicy() { return !SIG_POLICY_NONE.equals(getSignaturePolicy()); } protected void addSigningCertificate(Document doc, Element signedProperties, Input input) throws Exception { //NOPMD if (getSigningCertificate() == null && (getSigningCertificateChain() == null || getSigningCertificateChain().length == 0)) { return; } // signed certificate Element signedCertificate = createElement("SigningCertificate", doc, input); signedProperties.appendChild(signedCertificate); if (getSigningCertificate() != null) { LOG.debug("Adding signing certificate"); X509Certificate cert = getSigningCertificate(); addCertificate(cert, signedCertificate, doc, 0, input); } else if (getSigningCertificateChain() != null && getSigningCertificateChain().length > 0) { Certificate[] certs = getSigningCertificateChain(); int index = 0; for (Certificate cert : certs) { LOG.debug("Adding chain certtificate {}", index); X509Certificate x509Cert = (X509Certificate) cert; addCertificate(x509Cert, signedCertificate, doc, index, input); index++; } } else { // cannot happen throw new IllegalStateException("Unexpected exception"); } } /** * Returns the signing certificate. If you want to have a * "SigningCertificate" element then either this method or the method * {@link #getSigningCertificateChain()} must return a value which is * different from <code>null</code> or an empty array. * <p> * This implementation returns <code>null</code> */ protected X509Certificate getSigningCertificate() throws Exception { //NOPMD return null; } /** * Returns the signing certificate. If you want to have a * "SigningCertificate" element then either this method or the method * {@link #getSigningCertificate()} must return a value. * <p> * This implementation returns <code>null</code> */ protected X509Certificate[] getSigningCertificateChain() throws Exception { //NOPMD return null; } protected void addSigningTime(Document doc, Element signedProperties, Input input) { if (isAddSigningTime()) { LOG.debug("Adding signing time"); //signing time Element signingTime = createElement("SigningTime", doc, input); signedProperties.appendChild(signingTime); Date current = new Date(); signingTime.setTextContent(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX").format(current)); } } protected void addCertificate(X509Certificate cert, Element signedCertificate, Document doc, int index, Input input) throws CertificateEncodingException, NoSuchAlgorithmException, XmlSignatureException { Element elCert = createElement("Cert", doc, input); signedCertificate.appendChild(elCert); String algorithm = getMessageDigestAlgorithm(getDigestAlgorithmForSigningCertificate(), "The digest algorithm '%s' for the signing certificate is invalid"); String digest = calculateDigest(algorithm, cert.getEncoded()); Element certDigest = createElement("CertDigest", doc, input); elCert.appendChild(certDigest); Element digestMethod = createDigSigElement("DigestMethod", doc, input.getPrefixForXmlSignatureNamespace()); certDigest.appendChild(digestMethod); setAttribute(digestMethod, "Algorithm", getDigestAlgorithmForSigningCertificate()); Element digestValue = createDigSigElement("DigestValue", doc, input.getPrefixForXmlSignatureNamespace()); certDigest.appendChild(digestValue); digestValue.setTextContent(digest); Element issuerSerial = createElement("IssuerSerial", doc, input); elCert.appendChild(issuerSerial); Element x509IssuerName = createDigSigElement("X509IssuerName", doc, input.getPrefixForXmlSignatureNamespace()); issuerSerial.appendChild(x509IssuerName); x509IssuerName.setTextContent(cert.getIssuerX500Principal().getName(X500Principal.RFC2253)); Element x509SerialNumber = createDigSigElement("X509SerialNumber", doc, input.getPrefixForXmlSignatureNamespace()); issuerSerial.appendChild(x509SerialNumber); x509SerialNumber.setTextContent(cert.getSerialNumber().toString()); List<String> uris = getSigningCertificateURIs(); if (!uris.isEmpty() && uris.size() > index) { String uri = uris.get(index); if (uri != null && !uri.isEmpty()) { setAttribute(elCert, "URI", uri); } } } protected String getMessageDigestAlgorithm(String xmlSigDigestMethod, String errorMessage) throws XmlSignatureException { String algorithm; if (DigestMethod.SHA1.equals(xmlSigDigestMethod)) { algorithm = "SHA-1"; } else if (DigestMethod.SHA256.equals(xmlSigDigestMethod)) { algorithm = "SHA-256"; } else if ("http://www.w3.org/2001/04/xmldsig-more#sha384".equals(xmlSigDigestMethod)) { algorithm = "SHA-384"; } else if (DigestMethod.SHA512.equals(getDigestAlgorithmForSigningCertificate())) { algorithm = "SHA-512"; } else { throw new XmlSignatureException(String.format(errorMessage, xmlSigDigestMethod)); } return algorithm; } protected String calculateDigest(String algorithm, byte[] bytes) throws NoSuchAlgorithmException, CertificateEncodingException { MessageDigest digest = MessageDigest.getInstance(algorithm); byte[] digestBytes = digest.digest(bytes); return new Base64().encodeAsString(digestBytes); } protected Element createDigSigElement(String localName, Document doc, String prefixForXmlSignatureNamespace) { Element el = doc.createElementNS("http://www.w3.org/2000/09/xmldsig#", localName); if (prefixForXmlSignatureNamespace != null && !prefixForXmlSignatureNamespace.isEmpty()) { el.setPrefix(prefixForXmlSignatureNamespace); } return el; } protected Element createElement(String localName, Document doc, Input input) { Element el = doc.createElementNS(findNamespace(input.getMessage()), localName); String p = findPrefix(input.getMessage()); if (p != null && !p.isEmpty()) { el.setPrefix(p); } return el; } }