Signature Mismatch Issue with XML Signature Verification (xades) in java

Hello Stack Overflow community,

I’m facing an issue with XML signature verification and need some help debugging my code.

I have three key classes, my signature didn’t verified i am also new to java and tried everything but no success. kindly point out my mistake and correct my code also. so signature can be verified and generated against xml . kindly help me please ISO 20022 xml format shall be digitally signed and verified from below code

<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.03"><AppHdr><Fr><FIId><FinInstnId><BICFI>ABCDEFGHIJKL</BICFI></FinInstnId></FIId></Fr><To><FIId><FinInstnId><BICFI>MNOPQRSTUVWXYZ</BICFI></FinInstnId></FIId></To><BizMsgIdr>Message123</BizMsgIdr><MsgDefIdr>abcd.1234</MsgDefIdr><CreDt>2024-08-15T12:00:00Z</CreDt></AppHdr></Document>
  1. XadesSigner (CREATE SIGNATURE)
  2. XadesSignatureVerifier (verify signature)
  3. NoUriDereferencer.java (implemented custom URIDereferencer )

verfifier logs

Dereferencing URI: #_f838896a-be99-44a1-8b37-af12e079be08
Dereferencing URI: #_98ff15ed-7328-4241-abd0-67021d18488f-signedprops
URI is null or not found in map: com.ibm.xml.crypto.dsig.dom.ReferenceImpl@b0cf108f
Using data for null URI: javax.xml.crypto.OctetStreamData@982a8d94
Core Validation Status: false
Signature failed core validation
Signature validation status: true
Reference validity status: true, Reference URI: [#_f838896a-be99-44a1-8b37-af12e079be08]
Reference validity status: true, Reference URI: [#_98ff15ed-7328-4241-abd0-67021d18488f-signedprops]
Reference validity status: false, Reference URI: [null]
Signature is invalid.

package xades;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import javax.xml.crypto.Data;
import javax.xml.crypto.NodeSetData;
import javax.xml.crypto.URIDereferencer;
import javax.xml.crypto.URIReference;
import javax.xml.crypto.XMLCryptoContext;
import javax.xml.crypto.XMLStructure;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.DigestMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.SignedInfo;
import javax.xml.crypto.dsig.XMLObject;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.crypto.dsig.keyinfo.X509IssuerSerial;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class XadesSigner {

    public Document sign(Document doc, X509Certificate signerCertificate, PrivateKey privateKey, boolean debugLog) throws Exception {

        final String xadesNS = "http://uri.etsi.org/01903/v1.3.2#";
        final String signedpropsIdSuffix = "-signedprops";

        XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");

        // 1. Prepare KeyInfo
        KeyInfoFactory kif = fac.getKeyInfoFactory();
        X509IssuerSerial x509is = kif.newX509IssuerSerial(
                signerCertificate.getIssuerX500Principal().toString(),
                signerCertificate.getSerialNumber());
        X509Data x509data = kif.newX509Data(Collections.singletonList(x509is));
        final String keyInfoId = "_" + UUID.randomUUID().toString();
        KeyInfo ki = kif.newKeyInfo(Collections.singletonList(x509data), keyInfoId);

        // 2. Prepare references
        List<Reference> refs = new ArrayList<>();

        Reference ref1 = fac.newReference("#" + keyInfoId, 
            fac.newDigestMethod(DigestMethod.SHA256, null),
            Collections.singletonList(fac.newCanonicalizationMethod(
                CanonicalizationMethod.EXCLUSIVE, (XMLStructure) null)),
            null, null);
        refs.add(ref1);

        final String signedpropsId = "_" + UUID.randomUUID().toString() + signedpropsIdSuffix;

        Reference ref2 = fac.newReference("#" + signedpropsId,
            fac.newDigestMethod(DigestMethod.SHA256, null),
            Collections.singletonList(fac.newCanonicalizationMethod(
                CanonicalizationMethod.EXCLUSIVE, (XMLStructure) null)),
            "http://uri.etsi.org/01903/v1.3.2#SignedProperties", null);
        refs.add(ref2);

        Reference ref3 = fac.newReference(null, 
            fac.newDigestMethod(DigestMethod.SHA256, null),
            Collections.singletonList(fac.newCanonicalizationMethod(
                CanonicalizationMethod.EXCLUSIVE, (XMLStructure) null)),
            null, null);
        refs.add(ref3);

        SignedInfo si = fac.newSignedInfo(
            fac.newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE, (XMLStructure) null),
            fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null), refs);

        // 3. Create element AppHdr/Sgntr that will contain the <ds:Signature>
        Node appHdr = null;
        NodeList sgntrList = doc.getElementsByTagName("AppHdr");
        if (sgntrList.getLength() != 0)
            appHdr = sgntrList.item(0);

        if (appHdr == null)
            throw new Exception("Mandatory element AppHdr is missing in the document to be signed");

        Node sgntr = appHdr.appendChild(doc.createElementNS(appHdr.getNamespaceURI(), "Sgntr"));

        DOMSignContext dsc = new DOMSignContext(privateKey, sgntr);
        if (debugLog) {
            dsc.setProperty("javax.xml.crypto.dsig.cacheReference", Boolean.TRUE);
        }
        dsc.putNamespacePrefix(XMLSignature.XMLNS, "ds");

        // 4. Set up <ds:Object> with <QualifyingProperties> inside that includes SigningTime
        Element QPElement = doc.createElementNS(xadesNS, "xades:QualifyingProperties");
        QPElement.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xades", xadesNS);

        Element SPElement = doc.createElementNS(xadesNS, "xades:SignedProperties");
        SPElement.setAttributeNS(null, "Id", signedpropsId);
        dsc.setIdAttributeNS(SPElement, null, "Id");
        SPElement.setIdAttributeNS(null, "Id", true);
        QPElement.appendChild(SPElement);

        Element SSPElement = doc.createElementNS(xadesNS, "xades:SignedSignatureProperties");
        SPElement.appendChild(SSPElement);

        final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
        String signingTime = df.format(new Date());

        Element STElement = doc.createElementNS(xadesNS, "xades:SigningTime");
        STElement.appendChild(doc.createTextNode(signingTime));
        SSPElement.appendChild(STElement);

        DOMStructure qualifPropStruct = new DOMStructure(QPElement);

        List<DOMStructure> xmlObj = new ArrayList<>();
        xmlObj.add(qualifPropStruct);
        XMLObject object = fac.newXMLObject(xmlObj, null, null, null);

        List<XMLObject> objects = Collections.singletonList(object);

        // 5. Set up custom URIDereferencer to process Reference without URI
        final NodeList docNodes = doc.getElementsByTagName("Document");
        final Node docNode = docNodes.item(0);

        ByteArrayOutputStream refOutputStream = new ByteArrayOutputStream();
        Transformer xform = TransformerFactory.newInstance().newTransformer();
        xform.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        xform.transform(new DOMSource(docNode), new StreamResult(refOutputStream));
        InputStream refInputStream = new ByteArrayInputStream(refOutputStream.toByteArray()); 
        dsc.setURIDereferencer(new NoUriDereferencer(refInputStream));

        // 6. Sign it!
        XMLSignature signature = fac.newXMLSignature(si, ki, objects, null, null);
        signature.sign(dsc);

        // 7. For debug purposes, log each reference's digest and transformed data
        if (debugLog) {
            int i = 0;
            for (Reference ref : refs) {
                StringBuilder sb = new StringBuilder();
                String digValStr = digestToString(ref.getDigestValue());
                InputStream is = ref.getDigestInputStream();
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = is.read(buffer)) != -1) {
                    sb.append(new String(buffer, 0, bytesRead));
                }
                is.close();
                i++;
                System.out.println("ref #" + i + " URI: [" + ref.getURI() + "], digest: " + digValStr + ", transformed data: [" + sb.toString() + "]");
            }
        }

        return doc;
    }

    private static String digestToString(byte[] digest) {
        StringBuilder sb = new StringBuilder();
        for (byte b : digest) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

}

package xades;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.StringReader;
import java.security.Key;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.xml.crypto.AlgorithmMethod;
import javax.xml.crypto.KeySelector;
import javax.xml.crypto.KeySelectorException;
import javax.xml.crypto.KeySelectorResult;
import javax.xml.crypto.XMLCryptoContext;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.OutputKeys;

public class XadesSignatureVerifier {

    private X509Certificate signerCertificate;

    public XadesSignatureVerifier(X509Certificate signerCertificate) {
        this.signerCertificate = signerCertificate;
    }

    public SignatureInfo verify(String dataPDU, boolean debugLog) throws Exception {
        SignatureInfo sigInfo = new SignatureInfo();
        XPath xpath = XPathFactory.newInstance().newXPath();

        if (debugLog) {
            System.out.println("Starting verification process...");
        }

        // Parse the input XML document
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document doc = db.parse(new InputSource(new StringReader(dataPDU)));

        if (debugLog) {
            System.out.println("Original Document XML:n" + convertDocumentToString(doc));
            System.out.println("Original Document Root Element: " + doc.getDocumentElement().getTagName());
        }

        // Locate the Signature node
        String xpathExpression = "//*[local-name()='Signature']";
        NodeList nodes = (NodeList) xpath.evaluate(xpathExpression, doc.getDocumentElement(), XPathConstants.NODESET);

        if (nodes == null || nodes.getLength() == 0) {
            throw new Exception("Signature is missing in the document");
        }

        Node nodeSignature = nodes.item(0);

        if (debugLog) {
            System.out.println("Found Signature Node: " + nodeSignature.getNodeName());
        }

        // Create a KeySelector to retrieve the public key
        final KeySelector keySelector = new KeySelector() {
            @Override
            public KeySelectorResult select(KeyInfo keyInfo, KeySelector.Purpose purpose, AlgorithmMethod method, XMLCryptoContext context) throws KeySelectorException {
                return () -> signerCertificate.getPublicKey();
            }
        };

        // Set up XMLSignatureFactory and validation context
        XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
        DOMValidateContext valContext = new DOMValidateContext(keySelector, nodeSignature);

        final NodeList docNodes = doc.getElementsByTagName("Document");
        final Node docNode = docNodes.item(0);

        if (debugLog) {
            System.out.println("Document Node:n" + convertNodeToString(docNode));
        }

        // Set URI Dereferencer
        ByteArrayOutputStream refOutputStream = new ByteArrayOutputStream();
        Transformer xform = TransformerFactory.newInstance().newTransformer();
        xform.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        xform.transform(new DOMSource(docNode), new StreamResult(refOutputStream));
        InputStream refInputStream = new ByteArrayInputStream(refOutputStream.toByteArray());
        valContext.setURIDereferencer(new NoUriDereferencer(refInputStream));

        // Validate SignedProperties element
        NodeList nl = doc.getElementsByTagNameNS("http://uri.etsi.org/01903/v1.3.2#", "SignedProperties");
        if (nl.getLength() == 0) {
            throw new Exception("SignedProperties is missing in signature");
        }
        Element elemSignedProps = (Element) nl.item(0);
        valContext.setIdAttributeNS(elemSignedProps, null, "Id");

        // Validate the signature
        XMLSignature signature = fac.unmarshalXMLSignature(valContext);
        boolean coreValidity = signature.validate(valContext);
        sigInfo.setValid(coreValidity);

        if (debugLog) {
            System.out.println("Core Validation Status: " + coreValidity);
        }

        if (!coreValidity) {
            sigInfo.setValidationErrors("Signature failed core validation");
            System.out.println("Signature failed core validation");

            if (debugLog) {
                boolean sv = signature.getSignatureValue().validate(valContext);
                sigInfo.setValidationErrors("Signature validation status: " + sv);
                System.out.println("Signature validation status: " + sv);

                Iterator<?> i = signature.getSignedInfo().getReferences().iterator();
                List<Boolean> refValidityList = new ArrayList<>();
                while (i.hasNext()) {
                    Reference ref = (Reference) i.next();
                    boolean refValid = ref.validate(valContext);
                    refValidityList.add(refValid);
                    String refUri = ref.getURI();
                    if (refUri == null) {
                        refUri = "null";
                    }
                    sigInfo.setValidationErrors("Reference validity status: " + refValid + ", Reference URI: [" + refUri + "]");
                    System.out.println("Reference validity status: " + refValid + ", Reference URI: [" + refUri + "]");
                }
                sigInfo.setReferenceValidity(refValidityList);
            }
        }

        return sigInfo;
    }

    private String convertDocumentToString(Document doc) throws Exception {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        Transformer transformer = TransformerFactory.newInstance().newTransformer();
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.transform(new DOMSource(doc), new StreamResult(outputStream));
        return new String(outputStream.toByteArray());
    }

    private String convertNodeToString(Node node) throws Exception {
        Transformer transformer = TransformerFactory.newInstance().newTransformer();
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        transformer.transform(new DOMSource(node), new StreamResult(outputStream));
        return new String(outputStream.toByteArray());
    }
}

package xades;

import java.io.InputStream;

import javax.xml.crypto.Data;
import javax.xml.crypto.OctetStreamData;
import javax.xml.crypto.URIReference;
import javax.xml.crypto.URIReferenceException;
import javax.xml.crypto.URIDereferencer;
import javax.xml.crypto.XMLCryptoContext;
import javax.xml.crypto.dsig.XMLSignatureFactory;

public class NoUriDereferencer implements URIDereferencer {

    private InputStream inputStream;

    public NoUriDereferencer(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    @Override
    public Data dereference(URIReference uriRef, XMLCryptoContext ctx) 
            throws URIReferenceException {
        String uri = uriRef.getURI();

        if (uri != null) {
            System.out.println("Dereferencing URI: " + uri);
            URIDereferencer defaultDereferencer =
                    XMLSignatureFactory.getInstance("DOM").getURIDereferencer();

            try {
                // Utilize the default dereferencer if a URI is present
                return defaultDereferencer.dereference(uriRef, ctx);
            } catch (URIReferenceException e) {
                System.err.println("Error dereferencing URI: " + uri);
                e.printStackTrace();
                throw e;
            }
        } else {
            // Handle the case where the URI is null or not found
            System.out.println("URI is null or not found in map: " + uriRef);
            Data data = new OctetStreamData(inputStream);
            System.out.println("Using data for null URI: " + data.toString());
            return data;
        }
    }
}

New contributor

SJS is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

1

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật