/**
* 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;
}
}