From 8ac7f59644f1ad28a264b2654b8de21558bc5db6 Mon Sep 17 00:00:00 2001 From: RIHTARSIC Joze Date: Sun, 24 Mar 2024 16:30:34 +0100 Subject: [PATCH 1/2] Implementation pre-post processing extension with xades (basic) example. --- pom.xml | 2 + src/main/java/module-info.java | 4 + .../jcp/xml/dsig/internal/dom/DOMUtils.java | 76 ++- .../extension/SignatureProcessor.java | 42 ++ .../exceptions/ExtensionException.java | 47 ++ .../extension/xades/XAdESConstants.java | 40 ++ .../XAdESQualifyingPropertiesBuilder.java | 280 +++++++++ .../xades/XAdESSignatureProcessor.java | 166 ++++++ .../xml/security/signature/XMLSignature.java | 76 ++- .../utils/jaxb/DatatypeConverter.java | 112 ++++ .../utils/jaxb/OffsetDateAdapter.java | 39 ++ .../utils/jaxb/OffsetDateTimeAdapter.java | 39 ++ .../schemas/XAdES01903v132-201601.xsd | 535 ++++++++++++++++++ .../schemas/XAdES01903v141-202107.xsd | 67 +++ src/main/resources/bindings/xades.xjb | 23 + .../dom/signature/XAdESSignatureTest.java | 196 +++++++ .../security/samples/input/keystore-chain.p12 | Bin 0 -> 39998 bytes 17 files changed, 1739 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/apache/xml/security/extension/SignatureProcessor.java create mode 100644 src/main/java/org/apache/xml/security/extension/exceptions/ExtensionException.java create mode 100644 src/main/java/org/apache/xml/security/extension/xades/XAdESConstants.java create mode 100644 src/main/java/org/apache/xml/security/extension/xades/XAdESQualifyingPropertiesBuilder.java create mode 100644 src/main/java/org/apache/xml/security/extension/xades/XAdESSignatureProcessor.java create mode 100644 src/main/java/org/apache/xml/security/utils/jaxb/DatatypeConverter.java create mode 100644 src/main/java/org/apache/xml/security/utils/jaxb/OffsetDateAdapter.java create mode 100644 src/main/java/org/apache/xml/security/utils/jaxb/OffsetDateTimeAdapter.java create mode 100644 src/main/resources/bindings/schemas/XAdES01903v132-201601.xsd create mode 100644 src/main/resources/bindings/schemas/XAdES01903v141-202107.xsd create mode 100644 src/main/resources/bindings/xades.xjb create mode 100644 src/test/java/org/apache/xml/security/test/dom/signature/XAdESSignatureTest.java create mode 100644 src/test/resources/org/apache/xml/security/samples/input/keystore-chain.p12 diff --git a/pom.xml b/pom.xml index e91197507..18595d7cc 100644 --- a/pom.xml +++ b/pom.xml @@ -366,6 +366,7 @@ bindings/schemas/xenc-schema.xsd bindings/schemas/xenc-schema-11.xsd bindings/schemas/rsa-pss.xsd + bindings/schemas/XAdES01903v141-202107.xsd ${basedir}/src/main/resources/bindings/ @@ -377,6 +378,7 @@ security-config.xjb xop.xjb rsa-pss.xjb + xades.xjb ${basedir}/src/main/resources/bindings/bindings.cat false diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index cbd67652d..163ca5c0e 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -63,6 +63,8 @@ opens org.apache.xml.security; opens org.apache.xml.security.binding.excc14n; opens org.apache.xml.security.binding.xmldsig; + opens org.apache.xml.security.binding.xmldsig.xades.v132; + opens org.apache.xml.security.binding.xmldsig.xades.v141; opens org.apache.xml.security.binding.xmldsig11; opens org.apache.xml.security.binding.xmlenc; opens org.apache.xml.security.binding.xmlenc11; @@ -74,4 +76,6 @@ opens org.apache.xml.security.keys.storage.implementations; opens org.apache.xml.security.transforms.implementations; opens org.apache.xml.security.utils.resolver.implementations; + opens org.apache.xml.security.utils.jaxb; + exports org.apache.xml.security.extension.exceptions; } diff --git a/src/main/java/org/apache/jcp/xml/dsig/internal/dom/DOMUtils.java b/src/main/java/org/apache/jcp/xml/dsig/internal/dom/DOMUtils.java index 6955c0d46..615c0738e 100644 --- a/src/main/java/org/apache/jcp/xml/dsig/internal/dom/DOMUtils.java +++ b/src/main/java/org/apache/jcp/xml/dsig/internal/dom/DOMUtils.java @@ -34,11 +34,14 @@ import javax.xml.crypto.dsig.spec.XPathFilterParameterSpec; import javax.xml.crypto.dsig.spec.XPathType; import javax.xml.crypto.dsig.spec.XSLTTransformParameterSpec; +import javax.xml.namespace.QName; -import org.w3c.dom.Attr; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.JAXBElement; +import jakarta.xml.bind.JAXBException; +import jakarta.xml.bind.Marshaller; +import org.apache.xml.security.binding.xmldsig.xades.v132.QualifyingPropertiesType; +import org.w3c.dom.*; /** * Useful static DOM utility methods. @@ -46,6 +49,10 @@ */ public final class DOMUtils { + // most common attributes for id attribute names in XML + private static final List ID_ATTRIBUTE_NAMES = List.of("Id", "ID", "id"); + + // class cannot be instantiated private DOMUtils() {} @@ -422,4 +429,65 @@ public static boolean isNamespace(Node node) } return false; } + + /** + * This method convert JAXB object to XML Element value and add it to the + * target node. The root object of JAXB object has name as defined in + * objectQName. + * The method also sets ID flag to IDs in the XML structure. + * + * @param target the node to which the XML structure should be added + * @param obj the object to be converted to Element value + * @param objectQName the QName of the object root element + * @return the created XML structure as a Node + * @throws JAXBException if an error occurs during the marshalling + */ + public static Node objectToXMLStructure(Node target, Object obj, QName objectQName) throws JAXBException { + + JAXBContext jc = JAXBContext.newInstance(obj.getClass()); + Marshaller jaxbMarshaller = jc.createMarshaller(); + JAXBElement jaxbElement = new JAXBElement( + objectQName, + QualifyingPropertiesType.class, obj); + jaxbMarshaller.marshal(jaxbElement, target); + // set idness to all elements so that they can be used as references + setIdFlagToIdAttributes(target); + return target.getFirstChild(); + } + + /** + * This method declares all attributes with names: ID, id Id to be a + * user-determined ID attribute. This affects the value of + * Attr.isId and the behavior of + * Document.getElementById. + * + * @param n Node to start from setting id attributes as Id attribute + */ + public static void setIdFlagToIdAttributes(Node n) { + setIdFlagToIdAttributes(n, ID_ATTRIBUTE_NAMES); + } + + /** + * This method declares all attributes with names in idAttributes to be a + * user-determined ID attribute. This affects the value of + * Attr.isId and the behavior of + * Document.getElementById. + * + * @param n Node to start from setting id attributes as Id attribute + * @param idAttributes List of attribute names to be set as Id attribute + */ + public static void setIdFlagToIdAttributes(Node n, List idAttributes) { + if (n.getNodeType() == Node.ELEMENT_NODE) { + Element e = (Element) n; + idAttributes.forEach(id -> { + if (e.hasAttribute(id)) { + e.setIdAttribute(id, true); + } + }); + NodeList l = e.getChildNodes(); + for (int i = 0; i < l.getLength(); i++) { + setIdFlagToIdAttributes(l.item(i), idAttributes); + } + } + } } diff --git a/src/main/java/org/apache/xml/security/extension/SignatureProcessor.java b/src/main/java/org/apache/xml/security/extension/SignatureProcessor.java new file mode 100644 index 000000000..e8d4aa2fe --- /dev/null +++ b/src/main/java/org/apache/xml/security/extension/SignatureProcessor.java @@ -0,0 +1,42 @@ +/** + * 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.xml.security.extension; + +import org.apache.xml.security.signature.XMLSignature; +import org.apache.xml.security.signature.XMLSignatureException; + +/** + * This interface is responsible for processing signature. The implementation of + * this interface can be uses as pre-processors to add to the signature and + * additional data such as XAdES QualifyingProperties for the XAdES basic + * signatures profile. + * The implementation can be used as post-processors to add update the signatures + * after the signature has been generated. An example the Timestamp (TSA) of the + * signature, or automatic registration of the signature hast to blockchain ledger. + */ +public interface SignatureProcessor { + + /** + * Process the signature. + * + * @param signature the XMLSignature instance to be processed + * @throws XMLSignatureException if an error occurs while processing the signature + */ + void processSignature(XMLSignature signature) throws XMLSignatureException; +} diff --git a/src/main/java/org/apache/xml/security/extension/exceptions/ExtensionException.java b/src/main/java/org/apache/xml/security/extension/exceptions/ExtensionException.java new file mode 100644 index 000000000..e8c2d75d0 --- /dev/null +++ b/src/main/java/org/apache/xml/security/extension/exceptions/ExtensionException.java @@ -0,0 +1,47 @@ +/** + * 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.xml.security.extension.exceptions; + +/** + * This Exception is thrown while extension processing fails. + * + */ +public class ExtensionException extends Exception { + + private static final long serialVersionUID = 1L; + + /** + * Constructor ExtensionException + * + * @param message the message to display when this exception is thrown + */ + public ExtensionException(String message) { + super(message); + } + + /** + * Constructor ExtensionException + * + * @param message the message to display when this exception is thrown + * @param cause the cause of this exception + */ + public ExtensionException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/org/apache/xml/security/extension/xades/XAdESConstants.java b/src/main/java/org/apache/xml/security/extension/xades/XAdESConstants.java new file mode 100644 index 000000000..7c322098b --- /dev/null +++ b/src/main/java/org/apache/xml/security/extension/xades/XAdESConstants.java @@ -0,0 +1,40 @@ +/** + * 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.xml.security.extension.xades; + +/** + * Constants for the XAdES specification. + */ +public final class XAdESConstants { + private XAdESConstants() { + } + + // XAdES namespace and prefix + public static final String XADES_V132_NS = "http://uri.etsi.org/01903/v1.3.2#"; + public static final String XADES_V141_NS = "http://uri.etsi.org/01903/v1.4.1#"; + public static final String XADES_V132_PREFIX = "xades132"; + public static final String XADES_V141_PREFIX = "xades141"; + + /** SignedProperties reference type **/ + public static final String REFERENCE_TYPE_SIGNEDPROPERTIES = "http://uri.etsi.org/01903#SignedProperties"; + + /** Tag of Element CanonicalizationMethod **/ + public static final String _TAG_QUALIFYINGPROPERTIES = "QualifyingProperties"; + +} diff --git a/src/main/java/org/apache/xml/security/extension/xades/XAdESQualifyingPropertiesBuilder.java b/src/main/java/org/apache/xml/security/extension/xades/XAdESQualifyingPropertiesBuilder.java new file mode 100644 index 000000000..fbf5b69c3 --- /dev/null +++ b/src/main/java/org/apache/xml/security/extension/xades/XAdESQualifyingPropertiesBuilder.java @@ -0,0 +1,280 @@ +/** + * 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.xml.security.extension.xades; + +import org.apache.xml.security.algorithms.JCEMapper; +import org.apache.xml.security.binding.xmldsig.DigestMethodType; +import org.apache.xml.security.binding.xmldsig.X509IssuerSerialType; +import org.apache.xml.security.binding.xmldsig.xades.v132.*; +import org.apache.xml.security.extension.exceptions.ExtensionException; + +import javax.xml.datatype.DatatypeConfigurationException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.time.OffsetDateTime; + + +/** + * This class is used to build XAdES QualifyingProperties with compliance to + * XAdES-B-B (Basic Electronic Signature) structure. The lowest and simplest + * version just containing the SignedInfo, SignatureValue, KeyInfo and + * SignedProperties. This form extends the definition of an electronic signature + * to conform to the identified signature policy. + *

+ * The XAdESQualifyingPropertiesBuilder adds the following elements to [XMLDSIG]: + *

+ * QualifyingProperties
+ *     SignedProperties
+ *         SignedSignatureProperties
+ *             SigningTime
+ *             SigningCertificate
+ *             SignaturePolicyIdentifier
+ *             SignatureProductionPlace?
+ * 
+ *

+ * @see XAdES + * @see + * ETSI TS 101 903 V1.4.2 + */ +public class XAdESQualifyingPropertiesBuilder { + + String signatureId; + String xadesSignaturePropertiesId; + X509Certificate signingCertificate; + String certificateDigestMethodURI; + String signaturePolicy; + String signatureCity; + String signatureCountryName; + + protected XAdESQualifyingPropertiesBuilder() { + } + + /** + * Create a new instance of XAdESQualifyingPropertiesBuilder + * + * @return XAdESQualifyingPropertiesBuilder + */ + public static XAdESQualifyingPropertiesBuilder create() { + return new XAdESQualifyingPropertiesBuilder(); + } + + /** + * Set the signature identifier to be targeted from element + * QualifyingProperties/@Target + * + * @param signatureId - signature identifier + * @return XAdESQualifyingPropertiesBuilder for continued configuration + */ + public XAdESQualifyingPropertiesBuilder withSignatureId(String signatureId) { + this.signatureId = signatureId; + return this; + } + + + /** + * Set the identifier for the XAdES SignatureProperties element + * + * @param xadesSignaturePropertiesId - XAdES SignatureProperties identifier + * @return XAdESQualifyingPropertiesBuilder for continued configuration + */ + public XAdESQualifyingPropertiesBuilder withXAdESSignaturePropertiesId(String xadesSignaturePropertiesId) { + this.xadesSignaturePropertiesId = xadesSignaturePropertiesId; + return this; + } + + /** + * Set the signing certificate + * + * @param signingCertificate - signing certificate to be included in the + * XAdES QualifyingProperties + * @return XAdESQualifyingPropertiesBuilder for continued configuration + */ + public XAdESQualifyingPropertiesBuilder withSigningCertificate(X509Certificate signingCertificate) { + this.signingCertificate = signingCertificate; + return this; + } + + /** + * Set the digest method URI for the certificate + * + * @param digestURI - digest method URI for calculating the certificate digest + * @return XAdESQualifyingPropertiesBuilder for continued configuration + */ + public XAdESQualifyingPropertiesBuilder withCertificateDigestMethodURI(String digestURI) { + this.certificateDigestMethodURI = digestURI; + return this; + } + + /** + * Set the signature policy + * + * @param signaturePolicy - signature policy to be included in the + * XAdES QualifyingProperties + * @return XAdESQualifyingPropertiesBuilder for continued configuration + */ + public XAdESQualifyingPropertiesBuilder withSignaturePolicy(String signaturePolicy) { + this.signaturePolicy = signaturePolicy; + return this; + } + + /** + * Set the city where the signature was created (Optional) + * + * @param signatureCity - city where the signature was created + * @return XAdESQualifyingPropertiesBuilder for continued configuration + */ + public XAdESQualifyingPropertiesBuilder withSignatureCity(String signatureCity) { + this.signatureCity = signatureCity; + return this; + } + + /** + * Set the country name where the signature was created (Optional) + * + * @param signatureCountryName - country name where the signature was created + * @return XAdESQualifyingPropertiesBuilder for continued configuration + */ + public XAdESQualifyingPropertiesBuilder withSignatureCountryName(String signatureCountryName) { + this.signatureCountryName = signatureCountryName; + return this; + } + + /** + * Build the XAdES QualifyingProperties + * + * @return QualifyingPropertiesType + * @throws CertificateEncodingException + * @throws NoSuchAlgorithmException + * @throws DatatypeConfigurationException + */ + public QualifyingPropertiesType build() throws ExtensionException { + return createXAdESQualifyingProperties(signatureId, + xadesSignaturePropertiesId, + signingCertificate, + certificateDigestMethodURI, + signaturePolicy, + signatureCity, + signatureCountryName); + } + + + /** + * Method creates Signature/Object/QualifyingProperties/*SignedProperties* for signed certificate + * + * @param strSigPropId signed properties id + * @param cert, signing certificate + * @param digestUri digest method code (JCA provider code and W3c - URI) + * @param signatureReason value for: SignaturePolicyIdentifier/SignaturePolicyImplied - The + * signature policy is a set of rules for the creation and validation of an electronic signature, + * under which the signature can be determined to be valid. A given legal/contractual context may + * recognize a particular signature policy as meeting its requirements. + * @param sigCity city where signature was created + * @param sigCountryName country name where signature was created + * @return XAdES data structure: SignedProperties + */ + private SignedPropertiesType createSignedProperties(String strSigPropId, + X509Certificate cert, + String digestUri, + String signatureReason, + String sigCity, + String sigCountryName) throws ExtensionException { + SignedPropertiesType sp = new SignedPropertiesType(); + + sp.setId(strSigPropId); + CertIDListType scert = new CertIDListType(); + CertIDType sit = new CertIDType(); + DigestAlgAndValueType dt = new DigestAlgAndValueType(); + + MessageDigest md; + try { + md = MessageDigest.getInstance(JCEMapper.translateURItoJCEID(digestUri)); + } catch (NoSuchAlgorithmException ex) { + throw new ExtensionException("Message digest ["+digestUri+"] is not supported!", ex); + } + + byte[] der; + try { + der = cert.getEncoded(); + } catch (CertificateEncodingException ex) { + throw new ExtensionException("Certificate encoding error!", ex); + } + md.update(der); + dt.setDigestValue(md.digest()); + dt.setDigestMethod(new DigestMethodType()); + dt.getDigestMethod().setAlgorithm(digestUri); + sit.setCertDigest(dt); + sit.setIssuerSerial(new X509IssuerSerialType()); + sit.getIssuerSerial().setX509IssuerName(cert.getIssuerDN().getName()); + sit.getIssuerSerial().setX509SerialNumber(cert.getSerialNumber()); + SignedSignaturePropertiesType ssp = new SignedSignaturePropertiesType(); + ssp.setSigningTime(OffsetDateTime.now()); + ssp.setSigningCertificate(scert); + if (signatureReason != null){ + ssp.setSignaturePolicyIdentifier(new SignaturePolicyIdentifierType()); + ssp.getSignaturePolicyIdentifier().setSignaturePolicyImplied(signatureReason); + } + + if (sigCity != null || sigCountryName != null) { + ssp.setSignatureProductionPlace(new SignatureProductionPlaceType()); + ssp.getSignatureProductionPlace().setCity(sigCity); + ssp.getSignatureProductionPlace().setCountryName(sigCountryName); + } + + scert.getCert().add(sit); + sp.setSignedSignatureProperties(ssp); + return sp; + } + + + /** + * Method creates XAdESQualifyingProperties. Object QualifyingProperties must be stored into + * XMLdSIg Signature/Object element. + * + * @param sigId - signature id to which QualifyingProperties targets + * @param strSigPropId - id for created SignedProperties (part of QualifyingProperties) which must + * be signed + * @param cert, signing certificate + * @param digestURI digest method code (JCA provider code and W3c - URI) + * @param signaturePolicy - value for: SignaturePolicyIdentifier/SignaturePolicyImplied - The + * signature policy is a set of rules for the creation and validation ofan electronic signature, + * under which the signature can be determined to be valid. A given legal/contractual context may + * recognize a particular signature policy as meeting its requirements. + * @param signatureCity - city where signature was created + * @param signatureCountryName - country name where signature was created + * @return + * @throws CertificateEncodingException + * @throws NoSuchAlgorithmException + */ + public QualifyingPropertiesType createXAdESQualifyingProperties(String sigId, String strSigPropId, + X509Certificate cert, String digestURI, + String signaturePolicy, + String signatureCity, + String signatureCountryName) + throws ExtensionException { + + QualifyingPropertiesType qt = new QualifyingPropertiesType(); + qt.setTarget("#" + sigId); + qt.setSignedProperties(createSignedProperties(strSigPropId, cert, digestURI, signaturePolicy, + signatureCity, signatureCountryName)); + + return qt; + } +} diff --git a/src/main/java/org/apache/xml/security/extension/xades/XAdESSignatureProcessor.java b/src/main/java/org/apache/xml/security/extension/xades/XAdESSignatureProcessor.java new file mode 100644 index 000000000..7b2b4a1f2 --- /dev/null +++ b/src/main/java/org/apache/xml/security/extension/xades/XAdESSignatureProcessor.java @@ -0,0 +1,166 @@ +/** + * 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.xml.security.extension.xades; + +import jakarta.xml.bind.JAXBException; +import org.apache.xml.security.binding.xmldsig.xades.v132.QualifyingPropertiesType; +import org.apache.xml.security.encryption.XMLCipher; +import org.apache.xml.security.extension.SignatureProcessor; +import org.apache.xml.security.extension.exceptions.ExtensionException; +import org.apache.xml.security.signature.ObjectContainer; +import org.apache.xml.security.signature.XMLSignature; +import org.apache.xml.security.signature.XMLSignatureException; +import org.apache.xml.security.stax.impl.util.IDGenerator; +import org.apache.xml.security.transforms.TransformationException; +import org.apache.xml.security.transforms.Transforms; +import org.w3c.dom.Document; + +import javax.xml.namespace.QName; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; + +import static org.apache.jcp.xml.dsig.internal.dom.DOMUtils.objectToXMLStructure; +import static org.apache.xml.security.extension.xades.XAdESConstants.*; +import static org.apache.xml.security.extension.xades.XAdESQualifyingPropertiesBuilder.create; + +/** + * This class is responsible for pre-processing XAdES signature. + * It adds XAdES QualifyingProperties to the signature to be signed. + */ +public class XAdESSignatureProcessor implements SignatureProcessor { + private static final String ID_PREFIX_SIG = "sig-"; + private static final String ID_PREFIX_SIG_VAL = "sig-val-"; + private static final String ID_PREFIX_SIG_PROP = "sig-prop-"; + + // XAdES data + private X509Certificate signatureCertificate; + private String certificateDigestMethodURI; + private String signaturePolicy; + private String signatureCity; + private String signatureCountryName; + // List of transformations to be done before digesting + List referenceTransformAlgorithms = new ArrayList<>(); + + public XAdESSignatureProcessor(X509Certificate x509) { + this(x509, XMLCipher.SHA256, null, null, null); + } + + public XAdESSignatureProcessor(X509Certificate x509, String certificateDigestMethodURI, String signaturePolicy, String signatureCity, String signatureCountryName) { + this.signatureCertificate = x509; + this.certificateDigestMethodURI = certificateDigestMethodURI; + this.signaturePolicy = signaturePolicy; + this.signatureCity = signatureCity; + this.signatureCountryName = signatureCountryName; + } + + public void processSignature(XMLSignature signature) throws XMLSignatureException { + + if (isEmptyString(signature.getId())) { + signature.setId(IDGenerator.generateID(ID_PREFIX_SIG)); + } + // set signature value id to be ready for XAdES-T extension + if (isEmptyString(signature.getSignatureValueId())) { + signature.setSignatureValueId(IDGenerator.generateID(ID_PREFIX_SIG_VAL)); + } + + String strSigId = signature.getId(); + String strXAdESSigPropId = IDGenerator.generateID(ID_PREFIX_SIG_PROP); + Document doc = signature.getElement().getOwnerDocument(); + // set xades data + QualifyingPropertiesType qualifyingProperties; + + try { + // create XAdES QualifyingProperties + qualifyingProperties = create() + .withSignatureId(strSigId) + .withXAdESSignaturePropertiesId(strXAdESSigPropId) + .withSigningCertificate(signatureCertificate) + .withCertificateDigestMethodURI(certificateDigestMethodURI) + .withSignaturePolicy(signaturePolicy) + .withSignatureCity(signatureCity) + .withSignatureCountryName(signatureCountryName) + .build(); + } catch (ExtensionException e) { + throw new XMLSignatureException(e); + } + + // add XAdES QualifyingProperties to the signature + ObjectContainer objectContainer = new ObjectContainer(doc); + try { + objectContainer.appendChild(objectToXMLStructure(objectContainer.getElement(), + qualifyingProperties, + new QName(XADES_V132_NS, _TAG_QUALIFYINGPROPERTIES, XADES_V132_PREFIX))); + } catch (JAXBException e) { + throw new XMLSignatureException(e); + } + + signature.appendObject(objectContainer); + // add reference to the signed properties + Transforms transforms = getReferenceTransform(doc); + signature.addDocument("#" + strXAdESSigPropId, transforms, XMLCipher.SHA256, + null, REFERENCE_TYPE_SIGNEDPROPERTIES); + } + + private static boolean isEmptyString(String str) { + return str == null || str.isEmpty(); + } + + /** + * This method returns the reference transform algorithm. + * Optional list of transformations to be done before digesting + * + * @return the reference transform algorithm + */ + public List getReferenceTransformsAlgorithm() { + return referenceTransformAlgorithms; + } + + public void addReferenceTransformAlgorithm(String referenceTransformAlgorithm) { + this.referenceTransformAlgorithms.add(referenceTransformAlgorithm); + } + + public void removeReferenceTransformAlgorithm(String referenceTransformAlgorithm) { + this.referenceTransformAlgorithms.remove(referenceTransformAlgorithm); + } + + /** + * This method returns the Transforms object with the reference transform + * algorithm set. + * + * @param doc the document in which the Transforms object is created + * @return the Transforms object with the transform algorithm set or + * null if no transform algorithm is not set + * @throws XMLSignatureException if an error occurs during the creation of the Transforms object + */ + private Transforms getReferenceTransform(Document doc) throws XMLSignatureException { + if (referenceTransformAlgorithms.isEmpty()) { + return null; + } + Transforms transforms = new Transforms(doc); + try { + for (String referenceTransformAlgorithm : referenceTransformAlgorithms) { + transforms.addTransform(referenceTransformAlgorithm); + } + } catch (TransformationException e) { + throw new XMLSignatureException(e); + } + return transforms; + } +} diff --git a/src/main/java/org/apache/xml/security/signature/XMLSignature.java b/src/main/java/org/apache/xml/security/signature/XMLSignature.java index 6736ad0af..41d0772d6 100644 --- a/src/main/java/org/apache/xml/security/signature/XMLSignature.java +++ b/src/main/java/org/apache/xml/security/signature/XMLSignature.java @@ -27,12 +27,15 @@ import java.security.PublicKey; import java.security.cert.X509Certificate; import java.security.spec.AlgorithmParameterSpec; +import java.util.ArrayList; +import java.util.List; import javax.crypto.SecretKey; import org.apache.xml.security.algorithms.SignatureAlgorithm; import org.apache.xml.security.c14n.Canonicalizer; import org.apache.xml.security.exceptions.XMLSecurityException; +import org.apache.xml.security.extension.SignatureProcessor; import org.apache.xml.security.keys.KeyInfo; import org.apache.xml.security.keys.content.X509Data; import org.apache.xml.security.transforms.Transforms; @@ -213,6 +216,13 @@ public final class XMLSignature extends SignatureElementProxy { private static final Logger LOG = System.getLogger(XMLSignature.class.getName()); + // list of SignatureProcessor which are invoked before the signature is generated + // as example signature metadata generation such as XAdES + List preProcessors = new ArrayList<>(); + // list of SignatureProcessor which are invoked after the signature is generated + // as example timestamping + List postProcessors = new ArrayList<>(); + /** ds:Signature.ds:SignedInfo element */ private final SignedInfo signedInfo; @@ -614,6 +624,27 @@ public XMLSignature(Element element, String baseURI, boolean secureValidation, P this.state = MODE_VERIFY; } + /** + * Add signature processor which is invoked before the signature is generated. Purpose of the + * pre-processor is to validate and make everything ready before signature is executed as + * to add additional data to the signature. + * + * @param processor the signature pre-processor + */ + public void addPreProcessor(SignatureProcessor processor) { + preProcessors.add(processor); + } + + /** + * Add signature processor which is invoked after the signature is generated. Example of post-processor + * is timestamping of the signature. + * + * @param processor the signature processor + */ + public void addPostProcessor(SignatureProcessor processor) { + postProcessors.add(processor); + } + /** * Sets the Id attribute * @@ -622,6 +653,9 @@ public XMLSignature(Element element, String baseURI, boolean secureValidation, P public void setId(String id) { if (id != null) { setLocalIdAttribute(Constants._ATT_ID, id); + getElement().setIdAttributeNS(null, Constants._ATT_ID, true); + } else { + getElement().removeAttributeNS(null, Constants._ATT_ID); } } @@ -631,7 +665,41 @@ public void setId(String id) { * @return the Id attribute */ public String getId() { - return getLocalAttribute(Constants._ATT_ID); + return getElement().hasAttribute(Constants._ATT_ID)? + getLocalAttribute(Constants._ATT_ID):null; + } + + /** + * Sets the Id attribute to the SignatureValue Element. If the SignatureValue does not exist + * the attribute is not set. + */ + public void setSignatureValueId(String id) { + + if (signatureValueElement == null) { + // should not happen since the SignatureValue element is created in the constructor + LOG.log(Level.WARNING, "SignatureValue element is not available, cannot set id attribute [{}]", id); + return; + } + if (id == null) { + signatureValueElement.removeAttributeNS(null, Constants._ATT_ID); + return; + } + // set the attribute + signatureValueElement.setAttributeNS(null, Constants._ATT_ID, id); + signatureValueElement.setIdAttributeNS(null, Constants._ATT_ID, true); + } + /** + * Returns the Id attribute from the SignatureValue Element. If the SignatureValue does not exist + * or does not have an Id attribute, null is returned. + * + * @return the Id attribute from the SignatureValue Element + */ + public String getSignatureValueId() { + if (signatureValueElement == null) { + return null; + } + Attr signatureValueAttr = signatureValueElement.getAttributeNodeNS(null, Constants._ATT_ID); + return signatureValueAttr == null ? null : signatureValueAttr.getValue(); } /** @@ -781,6 +849,9 @@ public void sign(Key signingKey) throws XMLSignatureException { ); } + for (SignatureProcessor preProcessor : preProcessors) { + preProcessor.processSignature(this); + } //Create a SignatureAlgorithm object SignedInfo si = this.getSignedInfo(); SignatureAlgorithm sa = si.getSignatureAlgorithm(); @@ -803,6 +874,9 @@ public void sign(Key signingKey) throws XMLSignatureException { } catch (XMLSecurityException | IOException ex) { throw new XMLSignatureException(ex); } + for (SignatureProcessor p : postProcessors) { + p.processSignature(this); + } } /** diff --git a/src/main/java/org/apache/xml/security/utils/jaxb/DatatypeConverter.java b/src/main/java/org/apache/xml/security/utils/jaxb/DatatypeConverter.java new file mode 100644 index 000000000..ad63a9eb9 --- /dev/null +++ b/src/main/java/org/apache/xml/security/utils/jaxb/DatatypeConverter.java @@ -0,0 +1,112 @@ +/** + * 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.xml.security.utils.jaxb; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import static java.time.format.DateTimeFormatter.ISO_DATE; +import static java.time.format.DateTimeFormatter.ISO_DATE_TIME; + +/** + * Utility class for converting date and time values to and from string. The utility is used by JAXB adapters. + */ +public class DatatypeConverter { + @FunctionalInterface + private interface ConvertToOffsetDateTime { + OffsetDateTime method(String string); + } + + private static final System.Logger LOG = System.getLogger(DatatypeConverter.class.getName()); + + private static final List PARSER_FORMATS = Arrays.asList( + value -> OffsetDateTime.parse(value, ISO_DATE_TIME), + value -> { + LocalDateTime ldt = LocalDateTime.parse(value, ISO_DATE_TIME); + return ldt.atZone(ZoneId.systemDefault()).toOffsetDateTime(); + }, + value -> OffsetDateTime.parse(value, ISO_DATE), + value -> { + LocalDate ldt = LocalDate.parse(value, ISO_DATE); + return ldt.atStartOfDay(ZoneId.systemDefault()).toOffsetDateTime(); + }); + + protected DatatypeConverter() { + } + + public static OffsetDateTime parseDateTime(String dateTimeValue) { + String trimmedValue = trimToNull(dateTimeValue); + if (trimmedValue == null) { + return null; + } + + OffsetDateTime dateTime = PARSER_FORMATS.stream() + .map(parser -> parseDateTime(trimmedValue, parser)) + .filter(Objects::nonNull) + .findFirst().orElse(null); + + if (dateTime == null) { + LOG.log(System.Logger.Level.WARNING, "Can not parse date value [{}]. Value ingored!", + trimmedValue); + } + return dateTime; + } + + private static OffsetDateTime parseDateTime(String value, ConvertToOffsetDateTime parser) { + // first try to pase offset + try { + return parser.method(value); + } catch (DateTimeParseException ex) { + LOG.log(System.Logger.Level.WARNING, "Can not parse date [{}], Error: [{}]!", + value, ex.getMessage()); + } + return null; + } + + public static String printDateTime(OffsetDateTime value) { + return value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME); + } + + public static String printDate(OffsetDateTime value) { + return value.format(DateTimeFormatter.ISO_OFFSET_DATE); + } + + + /** + * Returns a none empty string whose value is this string, with all leading + * and trailing space removed, otherwise returns null. + * + * @param value the string to be trimmed + * @return the trimmed (not empty) string or null + */ + private static String trimToNull(String value) { + if (value == null) { + return null; + } + String result = value.trim(); + return result.isEmpty() ? null : result; + } +} diff --git a/src/main/java/org/apache/xml/security/utils/jaxb/OffsetDateAdapter.java b/src/main/java/org/apache/xml/security/utils/jaxb/OffsetDateAdapter.java new file mode 100644 index 000000000..83d349046 --- /dev/null +++ b/src/main/java/org/apache/xml/security/utils/jaxb/OffsetDateAdapter.java @@ -0,0 +1,39 @@ +/** + * 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.xml.security.utils.jaxb; + +import jakarta.xml.bind.annotation.adapters.XmlAdapter; + +import java.time.OffsetDateTime; + +/** + * Purpose of the class it to provide OffsetDateTime to string and string + * to OffsetDateTime conversion + */ +public class OffsetDateAdapter + extends XmlAdapter +{ + public OffsetDateTime unmarshal(String value) { + return (DatatypeConverter.parseDateTime(value)); + } + + public String marshal(OffsetDateTime value) { + return (DatatypeConverter.printDate(value)); + } +} diff --git a/src/main/java/org/apache/xml/security/utils/jaxb/OffsetDateTimeAdapter.java b/src/main/java/org/apache/xml/security/utils/jaxb/OffsetDateTimeAdapter.java new file mode 100644 index 000000000..e17eafbce --- /dev/null +++ b/src/main/java/org/apache/xml/security/utils/jaxb/OffsetDateTimeAdapter.java @@ -0,0 +1,39 @@ +/** + * 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.xml.security.utils.jaxb; + +import jakarta.xml.bind.annotation.adapters.XmlAdapter; + +import java.time.OffsetDateTime; + +/** + * Purpose of the class it to provide OffsetDateTime to string and string to OffsetDateTime conversion + * + */ +public class OffsetDateTimeAdapter + extends XmlAdapter +{ + public OffsetDateTime unmarshal(String value) { + return (DatatypeConverter.parseDateTime(value)); + } + + public String marshal(OffsetDateTime value) { + return (DatatypeConverter.printDateTime(value)); + } +} diff --git a/src/main/resources/bindings/schemas/XAdES01903v132-201601.xsd b/src/main/resources/bindings/schemas/XAdES01903v132-201601.xsd new file mode 100644 index 000000000..975201f04 --- /dev/null +++ b/src/main/resources/bindings/schemas/XAdES01903v132-201601.xsd @@ -0,0 +1,535 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/bindings/schemas/XAdES01903v141-202107.xsd b/src/main/resources/bindings/schemas/XAdES01903v141-202107.xsd new file mode 100644 index 000000000..5e393c82d --- /dev/null +++ b/src/main/resources/bindings/schemas/XAdES01903v141-202107.xsd @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/bindings/xades.xjb b/src/main/resources/bindings/xades.xjb new file mode 100644 index 000000000..6e70756a5 --- /dev/null +++ b/src/main/resources/bindings/xades.xjb @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + diff --git a/src/test/java/org/apache/xml/security/test/dom/signature/XAdESSignatureTest.java b/src/test/java/org/apache/xml/security/test/dom/signature/XAdESSignatureTest.java new file mode 100644 index 000000000..e373617e6 --- /dev/null +++ b/src/test/java/org/apache/xml/security/test/dom/signature/XAdESSignatureTest.java @@ -0,0 +1,196 @@ +/** + * 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.xml.security.test.dom.signature; + +import org.apache.xml.security.algorithms.JCEMapper; +import org.apache.xml.security.algorithms.SignatureAlgorithm; +import org.apache.xml.security.c14n.Canonicalizer; +import org.apache.xml.security.encryption.XMLCipher; +import org.apache.xml.security.extension.xades.XAdESSignatureProcessor; +import org.apache.xml.security.keys.KeyInfo; +import org.apache.xml.security.signature.XMLSignature; +import org.apache.xml.security.test.dom.DSNamespaceContext; +import org.apache.xml.security.test.dom.TestUtils; +import org.apache.xml.security.testutils.JDKTestUtils; +import org.apache.xml.security.transforms.Transforms; +import org.apache.xml.security.utils.Constants; +import org.apache.xml.security.utils.XMLUtils; +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.w3c.dom.Element; + +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathFactory; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.*; +import java.security.cert.X509Certificate; + +import static org.apache.jcp.xml.dsig.internal.dom.DOMUtils.setIdFlagToIdAttributes; + + +class XAdESSignatureTest { + + static { + if (!org.apache.xml.security.Init.isInitialized()) { + org.apache.xml.security.Init.init(); + } + } + + private static final String ECDSA_KS = + "src/test/resources/org/apache/xml/security/samples/input/keystore-chain.p12"; + private static final String ECDSA_KS_PASSWORD = "security"; + public static final String ECDSA_KS_TYPE = "PKCS12"; + + + @BeforeAll + public static void beforeAll() { + Security.insertProviderAt + (new org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI(), 1); + // Since JDK 15, the ECDSA algorithms are supported in the default java JCA provider. + // Add BouncyCastleProvider only for java versions before JDK 15. + boolean isNotJDK15up; + try { + int javaVersion = Integer.getInteger("java.specification.version", 0); + isNotJDK15up = javaVersion < 15; + } catch (NumberFormatException ex) { + isNotJDK15up = true; + } + + if (isNotJDK15up && Security.getProvider("BC") == null) { + // Use reflection to add new BouncyCastleProvider + try { + Class bouncyCastleProviderClass = Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider"); + Provider bouncyCastleProvider = (Provider) bouncyCastleProviderClass.getConstructor().newInstance(); + Security.addProvider(bouncyCastleProvider); + } catch (ReflectiveOperationException e) { + // BouncyCastle not installed, ignore + System.out.println("BouncyCastle not installed!"); + } + } + } + + @ParameterizedTest + @CsvSource({"rsa2048, http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", + "ed25519, http://www.w3.org/2021/04/xmldsig-more#eddsa-ed25519", + "ed448, http://www.w3.org/2021/04/xmldsig-more#eddsa-ed448", + "secp256r1, http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256", + "secp384r1, http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384", + "secp521r1, http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512"}) + void testXAdESSignatuire(String alias,String signatureAlgorithm) throws Exception { + String jceAlgorithm = JCEMapper.translateURItoJCEID(signatureAlgorithm); + Assumptions.assumeTrue(JDKTestUtils.isAlgorithmSupportedByJDK(jceAlgorithm)); + + KeyStore keyStore = KeyStore.getInstance(ECDSA_KS_TYPE); + keyStore.load(Files.newInputStream(Paths.get(ECDSA_KS)), ECDSA_KS_PASSWORD.toCharArray()); + + PrivateKey privateKey = + (PrivateKey) keyStore.getKey(alias, ECDSA_KS_PASSWORD.toCharArray()); + + doVerify(doSign(privateKey, (X509Certificate) keyStore.getCertificate(alias), + null, signatureAlgorithm, alias)); + } + + + private byte[] doSign(PrivateKey privateKey, X509Certificate x509, PublicKey publicKey, + String sigAlgURI, String alias) throws Exception { + + // generate test document for signing element + org.w3c.dom.Document doc = TestUtils.newDocument(); + doc.appendChild(doc.createComment(" Comment before ")); + Element root = doc.createElementNS("", "RootElement"); + doc.appendChild(root); + root.appendChild(doc.createTextNode("Some simple text\n")); + + Element canonElem = + XMLUtils.createElementInSignatureSpace(doc, Constants._TAG_CANONICALIZATIONMETHOD); + canonElem.setAttributeNS( + null, Constants._ATT_ALGORITHM, Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS + ); + + SignatureAlgorithm signatureAlgorithm = + new SignatureAlgorithm(doc, sigAlgURI); + XMLSignature sig = + new XMLSignature(doc, null, signatureAlgorithm.getElement(), canonElem); + root.appendChild(sig.getElement()); + doc.appendChild(doc.createComment(" Comment after ")); + + + Transforms transforms = new Transforms(doc); + transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE); + transforms.addTransform(Transforms.TRANSFORM_C14N_WITH_COMMENTS); + sig.addDocument("", transforms, XMLCipher.SHA256); + + if (x509 != null) { + sig.addKeyInfo(x509); + } else { + sig.addKeyInfo(publicKey); + } + // create XAdES processor + XAdESSignatureProcessor xadesProcessor = new XAdESSignatureProcessor(x509); + xadesProcessor.addReferenceTransformAlgorithm(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); + sig.addPreProcessor(xadesProcessor); + + sig.sign(privateKey); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + //XMLUtils.outputDOMc14nWithComments(doc, bos); + XMLUtils.outputDOM(doc.getDocumentElement(), bos); + + Files.write(Paths.get("target/XAdES-" + alias + ".xml"), bos.toByteArray()); + return bos.toByteArray(); + } + + private void doVerify(byte[] signedXml) throws Exception { + try (InputStream is = new ByteArrayInputStream(signedXml)) { + doVerify(is); + } + } + + private void doVerify(InputStream is) throws Exception { + org.w3c.dom.Document doc = XMLUtils.read(is, false); + setIdFlagToIdAttributes(doc.getDocumentElement()); + + XPathFactory xpf = XPathFactory.newInstance(); + XPath xpath = xpf.newXPath(); + xpath.setNamespaceContext(new DSNamespaceContext()); + + String expression = "//ds:Signature[1]"; + Element sigElement = + (Element) xpath.evaluate(expression, doc, XPathConstants.NODE); + XMLSignature signature = new XMLSignature(sigElement, ""); + + signature.addResourceResolver(new XPointerResourceResolver(sigElement)); + + KeyInfo ki = signature.getKeyInfo(); + if (ki == null) { + throw new RuntimeException("No keyinfo"); + } + X509Certificate cert = signature.getKeyInfo().getX509Certificate(); + if (cert != null) { + Assertions.assertTrue(signature.checkSignatureValue(cert)); + } else { + Assertions.assertTrue(signature.checkSignatureValue(signature.getKeyInfo().getPublicKey())); + } + } +} diff --git a/src/test/resources/org/apache/xml/security/samples/input/keystore-chain.p12 b/src/test/resources/org/apache/xml/security/samples/input/keystore-chain.p12 new file mode 100644 index 0000000000000000000000000000000000000000..4cc24ee5e508c7ea65b4ee10823ccfed4279f930 GIT binary patch literal 39998 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 QV2zet>=lXzGXer95S&Pwr~m)} literal 0 HcmV?d00001 From aa3423856935ae01ea8401dc39c8f8f7275fca7f Mon Sep 17 00:00:00 2001 From: RIHTARSIC Joze Date: Mon, 25 Mar 2024 13:01:29 +0100 Subject: [PATCH 2/2] [SANTUARIO-611] Update DOMUtils.objectToXMLStructure to be generic --- .../java/org/apache/jcp/xml/dsig/internal/dom/DOMUtils.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/apache/jcp/xml/dsig/internal/dom/DOMUtils.java b/src/main/java/org/apache/jcp/xml/dsig/internal/dom/DOMUtils.java index 615c0738e..d52cdd673 100644 --- a/src/main/java/org/apache/jcp/xml/dsig/internal/dom/DOMUtils.java +++ b/src/main/java/org/apache/jcp/xml/dsig/internal/dom/DOMUtils.java @@ -40,7 +40,6 @@ import jakarta.xml.bind.JAXBElement; import jakarta.xml.bind.JAXBException; import jakarta.xml.bind.Marshaller; -import org.apache.xml.security.binding.xmldsig.xades.v132.QualifyingPropertiesType; import org.w3c.dom.*; /** @@ -446,9 +445,9 @@ public static Node objectToXMLStructure(Node target, Object obj, QName objectQNa JAXBContext jc = JAXBContext.newInstance(obj.getClass()); Marshaller jaxbMarshaller = jc.createMarshaller(); - JAXBElement jaxbElement = new JAXBElement( + JAXBElement jaxbElement = new JAXBElement( objectQName, - QualifyingPropertiesType.class, obj); + obj.getClass(), obj); jaxbMarshaller.marshal(jaxbElement, target); // set idness to all elements so that they can be used as references setIdFlagToIdAttributes(target);