import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.crypto.*;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.StringWriter;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.HashSet;
import java.util.Set;

//This class must only be used for empty reference context
class SignatureURIDereferencer implements URIDereferencer {
   private final Document doc;
   private final Element signatureElement;

   public SignatureURIDereferencer(Document doc, Element signatureElement) {
       this.doc = doc;
       this.signatureElement = signatureElement;
   }

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

        if (uri == null || uri.isEmpty()) {
            try {
                // temporarily remove signature element
                Node parentNode = signatureElement.getParentNode();
                Node removedNode = parentNode.removeChild(signatureElement);

                // apply C14N transformation
                TransformerFactory tf = TransformerFactory.newInstance();
                Transformer trans = tf.newTransformer();
                StringWriter sw = new StringWriter();
                trans.transform(new DOMSource(doc), new StreamResult(sw));

                // add signature element back
                parentNode.appendChild(removedNode);

                return new OctetStreamData(new ByteArrayInputStream(sw.toString().getBytes()));
            } catch (Exception e) {
               throw new URIReferenceException("Dereference işlemi sırasında hata: " + e.getMessage(), e);
            }
        }

        throw new URIReferenceException("Unsupported uri: " + uri);

   }
}


// Function to set error response
void setErrorResponse(String code, String desc) {
   requestErrorMessageToTargetAPI = """<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 
   <soap:Body>  
      <soap:Fault>  
        <faultCode>${code}</faultCode>  
        <faultString>${desc}</faultString>  
      </soap:Fault>  
   </soap:Body>  
</soap:Envelope>""";
   statusCodeToTargetAPI = 401;
}

try {

    //IMPORTANT: Client identity must be specified before this policy execution

    // Check if client certificate is present
    if (credential_certificate == null) {
        setErrorResponse("ERR-01", "No certificate found for the sender member!");
        return;
    }

    // Check certificate validity
    try {
        credential_certificate.checkValidity();
    } catch (Exception e) {
        setErrorResponse("ERR-02", "Client certificate was expired!");
        return;
    }

    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setNamespaceAware(true);
    dbf.setXIncludeAware(false);
    dbf.setExpandEntityReferences(false);
    dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
    dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
    dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
    DocumentBuilder builder = dbf.newDocumentBuilder();
    Document doc = builder.parse(new ByteArrayInputStream(requestBodyTextFromClient.getBytes("UTF-8")));

    NodeList nl = doc.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Signature");
    if (nl.getLength() == 0) {
        setErrorResponse("ERR-03", "Request is not signed.");
        return;
    }

    Element signatureElement = (Element) nl.item(0);

    // gather ID's from references in signature
    Set<String> referencedIds = new HashSet<>();
    NodeList references = signatureElement.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Reference");
    for (int i = 0; i < references.getLength(); i++) {
        Element reference = (Element) references.item(i);
        String uri = reference.getAttribute("URI");
        if (uri != null && uri.startsWith("#")) {// process only fragment identifiers
           // remove # sign from uri
           referencedIds.add(uri.substring(1));
        }
        //ignore external references and empty uri
    }

    // set the id elements that are only referenced in signature
    NodeList allElements = doc.getElementsByTagName("*");
    for (int i = 0; i < allElements.getLength(); i++) {
        Element element = (Element) allElements.item(i);

        // wsu:Id attribute control
        if (element.hasAttributeNS("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", "Id")) {
            String idValue = element.getAttributeNS("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", "Id");
            if (referencedIds.contains(idValue)) {
               element.setIdAttributeNS("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", "Id", true);
            }
        }

        // Id attribute control
        if (element.hasAttribute("Id")) {
            String idValue = element.getAttribute("Id");
            if (referencedIds.contains(idValue)) {
               element.setIdAttribute("Id", true);
            }
        }

        // ID attribute control
        if (element.hasAttribute("ID")) {
            String idValue = element.getAttribute("ID");
            if (referencedIds.contains(idValue)) {
               element.setIdAttribute("ID", true);
            }
        }

        // id attribute control
        if (element.hasAttribute("id")) {
            String idValue = element.getAttribute("id");
            if (referencedIds.contains(idValue)) {
               element.setIdAttribute("id", true);
            }
        }
    }

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

    // Create signature context for empty uri references
    final PublicKey key = credential_certificate.getPublicKey();
    DOMValidateContext signatureContext = new DOMValidateContext(  key,  signatureElement );
    signatureContext.setURIDereferencer(new SignatureURIDereferencer(doc, signatureElement));

    // Create signature context for fragment uri references
    DOMValidateContext  refContext = new DOMValidateContext(  key,  signatureElement );

    // unmarshal signature
    javax.xml.crypto.dsig.XMLSignature signature = fac.unmarshalXMLSignature(signatureContext);

    // validate signature
    boolean signatureValid = signature.getSignatureValue().validate(signatureContext);

    // validate references
    boolean referencesValid = true;
    List<?> referenceList = signature.getSignedInfo().getReferences();
    for (Object ref : referenceList) {
        Reference reference = (Reference) ref;
        if (!StringUtils.isBlank(reference.getURI())) {
            boolean refValid = reference.validate(refContext);
            if (!refValid) {
               referencesValid = false;
            }
        }

    }


    // Both validation results must be success
    if( !(signatureValid && referencesValid) ){
        setErrorResponse("ERR-04", "Signature verification failed!"  );
        return;
    }

} catch (Exception e) {
   setErrorResponse("ERR-05", "Error during signature verification: " + e.getMessage());
   return;
}
GROOVY