xades4j: Sign only one element - java

I'm using this code to sign a xml document:
Document doc = getDocument(xml_to_sign);
Element elemToSign = doc.getDocumentElement();
String file_uri_path = elemToSign.getBaseURI();
DataObjectDesc obj1 = new DataObjectReference(file_uri_path).withType("http://www.gzs.si/shemas/eslog/racun/1.5#Racun");
SignedDataObjects dataObjs = new SignedDataObjects(obj1);
signer.sign(dataObjs, elemToSign);
xml_to_sign is the full path to the xml file.
The problem is, that I would like to sign only the node with the id "data" (#data), but append the signature to the node elemToSign.
Is it possible to do this with xades4j?

Yes, it is. The sign method's argument is the parent node, not the element to sign (it could be the same node, depending on the configured references). In your example you should add a reference for "#data":
Document doc = getDocument(xml_to_sign);
Element parent = doc.getDocumentElement();
DataObjectDesc obj1 = new DataObjectReference("#data").withType("http://www.gzs.si/shemas/eslog/racun/1.5#Racun");
SignedDataObjects dataObjs = new SignedDataObjects(obj1);
signer.sign(dataObjs, parent);
Another option is to add a reference for the whole XML document (empty URI) and use a XPath transform.

You should specify that the attribute named "Id" in your xml document is the XML ID attribute that Apache Santuario (used internally by Xades4j) will use in the getElementById() (as lgoncalves has pointed out in his commments to his own answer).
Element parent = doc.getDocumentElement();
parent.setIdAttribute("Id", true);
//or parent.setIdAttributeNS("http://your.name.space", "Id", true);
I had the same problem, and this additional line of code solved it.

Related

> and < gets converted to > and < while adding a xml like string in element.setTextContent()

I have a string which looks like an XML
Ex: String sample = "<GrpHdr><MsgId>MQSECJYJHRBPDTZTYNNEYXOZUPAUDEKVDFV</MsgId><CreDtTm>2023-02-02T21:48:58.075+05:30</CreDtTm></GrpHdr>";
I am trying to create an XML document with an element containing the above information:
Ex:
<ns1:TstCode>T</ns1:TstCode>
<ns1:FType>SCF</ns1:FType>
<ns1:FileRef>220811084023</ns1:FileRef>
<ns1:RoutingInd>ALL</ns1:RoutingInd>
<ns1:FileBusDt>2022-08-11</ns1:FileBusDt>
<ns1:FIToFI xmlns="urn:iso:std:iso:20022:tech:xsd">
<GrpHdr>
<MsgId>MQSECJYJHRBPDTZTYNNEYXOZUPAUDEKVDFV</MsgId>
<CreDtTm>2023-02-02T21:48:58.075+05:30</CreDtTm>
</GrpHdr>
</ns1:FIToFI>
When I create the document for the above XML using this code:
private static DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
DOMImplementation domImpl = db.getDOMImplementation();
private Document buildExampleDocumentWithNamespaces(DOMImplementation domImpl, String output) {
Document document = domImpl.createDocument("urn:Scf:xsd:$BlkCredTrf", "ns1:BlkCredTrf", null);
document.getDocumentElement().appendChild(document.createElement("ns1:TstCode")).setTextContent("T");
document.getDocumentElement().appendChild(document.createElement("ns1:FType")).setTextContent("SCF");
document.getDocumentElement().appendChild(document.createElement("ns1:FileRef")).setTextContent("220811084023");
document.getDocumentElement().appendChild(document.createElement("ns1:RoutingInd")).setTextContent("ALL");
document.getDocumentElement().appendChild(document.createElement("ns1:FileBusDt")).setTextContent("2022-08-11");
document.getDocumentElement().appendChild(document.createElementNS("urn:iso:std:iso:tech:xsd","ns1:FIToFI");
return document;
}
I do not have issues until this point.
When I try to add <GrpHdr><MsgId>MQSECJYJHRBPDTZTYNNEYXOZUPAUDEKVDFV</MsgId><CreDtTm>2023-02-02T21:48:58.075+05:30</CreDtTm></GrpHdr> as a Text content to the FIToFI tag at last, using the code:
document.getDocumentElement().appendChild(document.createElementNS("urn:iso:std:iso:tech:xsd","ns1:FIToFI").setTextContent(sample);
The XML gets created like this:
<ns1:TstCode>T</ns1:TstCode>
<ns1:FType>SCF</ns1:FType>
<ns1:FileRef>220811084023</ns1:FileRef>
<ns1:RoutingInd>ALL</ns1:RoutingInd>
<ns1:FileBusDt>2022-08-11</ns1:FileBusDt>
<ns1:FIToFI xmlns="urn:iso:std:iso:20022:tech:xsd">
<GrpHdr>
<MsgId>MQSECJYJHRBPDTZTYNNEYXOZUPAUDEKVDFV</MsgId>
<CreDtTm>2023-02-02T21:48:58.075+05:30</CreDtTm>
</GrpHdr>
</ns1:FIToFI>
Please help me to create this XML without the escape characters.
That the content is escaped is intended. When you set the text content of an element, any special character like < have to be escaped like <, otherwise the text content will be interpreted as other XML content like elements or comments. That's why setTextContent() will escape the content for you.
When you want to add an element instead, you use methods like appendChild() with an Element argument. Build your elements as usual with the createElement() method and add them together like this:
Element element = document.createElementNS("urn:iso:std:iso:tech:xsd","ns1:FIToFI");
Element grpHdr = document.createElement("GrpHdr");
Element msgId = document.createElement("MsgId");
msgId.setTextContent("MQSECJYJHRBPDTZTYNNEYXOZUPAUDEKVDFV");
grpHdr.appendChild(msgId);
Element creDtTm = document.createElement("CreDtTm");
creDtTm.setTextContent("2023-02-02T21:48:58.075+05:30");
grpHdr.appendChild(creDtTm);
element.appendChild(grpHdr);
document.getDocumentElement().appendChild(element);
This will add the XML element inside the other XML element.
When you have the inner XML as a string, parse the XML string with DocumentBuilder.parse() (see How to create a XML object from String in Java?) and import the Element with the Document.importNode() method (see org.w3c.dom.DOMException: WRONG_DOCUMENT_ERR: A node is used in a different document than the one that created it). The code can look like this:
String innerXml = "<GrpHdr><MsgId[...]eDtTm></GrpHdr>";
Document innerDocument = db.parse(new InputSource(new StringReader(innerXml)));
Element innerRootElement = innerDocument.getDocumentElement();
Node importedNode = document.importNode(innerRootElement, true);
element.appendChild(importedNode);

Batik copy element from one document to another

I'm trying to compose an svg with batik from elements from a source document. This works as long as the elements don't reference things defined in the defs section (like gradients or filters). But when a filter is referenced I get an exception. I tried to also copy over the defs section but that didn't help.
String parser = XMLResourceDescriptor.getXMLParserClassName();
SAXSVGDocumentFactory factory = new SAXSVGDocumentFactory(parser);
Document source = factory.createDocument("source", getClass().getResourceAsStream("/artwork/source.svg"));
SVGOMDocument target = (SVGOMDocument) domImpl.createDocument(svgNS, "svg", null);
Node defs = source.getElementsByTagName("defs").item(0).cloneNode(true);
target.adoptNode(defs);
target.getRootElement().appendChild(defs);
BridgeContext ctx = new BridgeContext(new UserAgentAdapter());
GVTBuilder builder = new GVTBuilder();
builder.build(ctx, target);
Element sourceEl = getElementByXPath(source,
"//*[#id='IMAGES']/*[#id='" + selection.getImageSet() + "']/*[#id='"
+ suit.abbreviation() + value + "-" + selection.getImageSet() + "']");
SVGOMElement complete = (SVGOMElement) sourceEl.cloneNode(true);
target.adoptNode(complete);
target.getDocumentElement().appendChild(complete);
Rectangle2D completeBBox = builder.build(ctx, complete).getSensitiveBounds();
The last line gives the exception. Any help would be appreciated.
EDIT:
If I save the target svg after adding the defs section and then reload it again it works. But I would like to avoid this aditional step.
I found a solution to avoid the extra save/load. Use importNode instead of clone/adopt for the defs.
Node defs = target.importNode(source.getElementsByTagName("defs").item(0), true);
target.getRootElement().appendChild(defs);

Parse CDATA from XML to enable editing Java

I am writing a simulator which communicates with a client's piece of software over a local socket. The communication language is XML. I have written some code which works - parsing the incoming XML string into Document via the DocumentBuilder interface.
I have been encountering a problem with CDATA (Having never seen it before). Basically, I need to access fields within the CDATA tag and change them. I load up a 'template' XML document (to reply to the messages with) and use values received in the first message inside the response. Some of the fields that need to be changed are in this CDATA tag (clear what I mean below).
public static String getOutputMessage(String input) throws Exception{
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
Document inputDoc, outputDoc;
Element messageElement = (Element)inputDoc.getElementsByTagName("TRANS").item(0);
messageType = messageElement.getAttribute("name");
if (messageType.equals("processTransaction")){
outputDoc = db.parse(path+"processTransaction\\posPrintReceipt.xml");
outputDoc = changeContent(outputDoc, "PAN_NUMBER", transaction.getPan_number());
outputDoc = changeContent(outputDoc, "TOKEN", transaction.getToken());
outputDoc = changeContent(outputDoc, "TOTAL_AMOUNT", transaction.getTotal_amount());
outputDoc = changeContent(outputDoc, "TRANSACTION_TIME", transaction.getTransaction_time());
outputDoc = changeContent(outputDoc, "TRANSACTION_DATE", transaction.getTransaction_date());
}
}
private static Document changeContent(Document doc,String tag,String value) {
System.out.println("Changing: ["+tag+" : "+value+"]");
NodeList nodes=doc.getElementsByTagName(tag);
Node node = nodes.item(0);
Node parent=node.getParentNode();
node.setTextContent(value);
System.out.println(doc.getElementsByTagName(tag).item(0) + " " + node.getTextContent());
parent.replaceChild(node, doc.getElementsByTagName(tag).item(0));
return doc;
}
The functions above work on normal Elements but below is an example XML message I have to read and change some values such as
<RLSOLVE_MSG version="5.0">
<MESSAGE>
<SOURCE_ID>DP01</SOURCE_ID>
<TRANS_NUM>000001</TRANS_NUM>
</MESSAGE>
<POI_MSG type="interaction">
<INTERACTION name="posPrintReceipt">
<RECEIPT type="merchant" format="xml">
<![CDATA[<RECEIPT>
<AUTH_CODE>06130</AUTH_CODE>
<CARD_SCHEME>VISA</CARD_SCHEME>
<CURRENCY_CODE>GBP</CURRENCY_CODE>
<CUSTOMER_PRESENCE>internet</CUSTOMER_PRESENCE>
<FINAL_AMOUNT>1.00</FINAL_AMOUNT>
<MERCHANT_NUMBER>8888888</MERCHANT_NUMBER>
<PAN_NUMBER>454420******0382</PAN_NUMBER>
<PAN_EXPIRY>12/15</PAN_EXPIRY>
<TERMINAL_ID>04176421</TERMINAL_ID>
<TOKEN>454420bbbbbkqrm0382</TOKEN>
<TOTAL_AMOUNT>1.00</TOTAL_AMOUNT>
<TRANSACTION_DATA_SOURCE>keyed</TRANSACTION_DATA_SOURCE>
<TRANSACTION_DATE>14/02/2014</TRANSACTION_DATE>
<TRANSACTION_NUMBER>000001</TRANSACTION_NUMBER>
<TRANSACTION_RESPONSE>06130</TRANSACTION_RESPONSE>
<TRANSACTION_TIME>17:13:17</TRANSACTION_TIME>
<TRANSACTION_TYPE>purchase</TRANSACTION_TYPE>
<VERIFICATION_METHOD>unknown</VERIFICATION_METHOD>
<DUPLICATE>false</DUPLICATE>
</RECEIPT>]]>
</RECEIPT>
</INTERACTION>
</POI_MSG>
CDATA is an encoding mechanism to include arbitrary data within an XML file. Everything within CDATA is parsed as a single string when loading the XML into a Document instance. If you need to access the contents of the CDATA as a DOM document, you will need to instantiate a second Document object from the string contents, make your changes, then serialize that back to a string and put the string back into a CDATA in the original document.
I dont think CDATA section will be parsed as other regular elements in the XML. CDATA section is purely to escape any syntax checks. My suggestion would be use a element to represent the data in CDATA section. If you still want to use CDATA section, I guess you'll need parse the section as a string and then load the data into a Document.

getChildNodes() returns NULL for my root in XML

My goal : to get the root of the XML in a Node object , and then evaluate it !
My problem :
I'm trying to evaluate my expression from the ROOT of the XML file, I have this method ( I need to implement is) :
public Object evaluate(String expression, QName returnType);
Assume that I've already opened the XML with Document , like this :
//load the document into a DOM Document
this.domFactory = DocumentBuilderFactory.newInstance();
domFactory.setNamespaceAware(true); // never forget this!
this.builder = domFactory.newDocumentBuilder();
this.doc = builder.parse("books.xml");
//create an XPath factory
this.factory = XPathFactory.newInstance();
//create an XPath Object
this.xpath = factory.newXPath();
Now when I do this , inside public Object evaluate(String expression, QName returnType);
:
String unitedString = " my characters " ;
rootNode = doc.getChildNodes().item(0);
System.out.println(rootNode.getNodeName()); // this line presents the name of the root
Object returnedObject= xpath.evaluate(unitedString,rootNode ,returnType); // this line makes eclipse go crazy
Eclispe says after line "4" : " DTMManagerDefault.getDTMHandleFromNode(Node) line: not available "
But in line "3" , Eclipse produces the name of the root , which is inventory ...
So where did I go wrong ?
What's wrong with it ?
Thank you all , Jack
first:
Your unitedString is not a valid xPath expression, it should be something like /root/node/ xPath's describe paths to (a) specific node(s) to in your xml file.
second:
The root node of any xml is a special node called the DocumentElement, you can get to it by calling doc.getDocumentElement()

OpenSAML custom attribute value

I'm trying to create a SAML response. One of the attributes that makes up the assertion is called address and the attribute value needs to be a custom type that is defined in an XSD. How do I add custom attribute value types to the response?
If your attribute value XML is in String form:
String yourXMLFragment = "...";
AttributeStatementBuilder attributeStatementBuilder =
(AttributeStatementBuilder) builderFactory.getBuilder(AttributeStatement.DEFAULT_ELEMENT_NAME);
AttributeStatement attributeStatement = attributeStatementBuilder.buildObject();
AttributeBuilder attributeBuilder =
(AttributeBuilder) builderFactory.getBuilder(Attribute.DEFAULT_ELEMENT_NAME);
Attribute attr = attributeBuilder.buildObject();
attr.setName("yourAttributeName");
XSAnyBuilder sb2 = (XSAnyBuilder) builderFactory.getBuilder(XSAny.TYPE_NAME);
XSAny attrAny = sb2.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSAny.TYPE_NAME);
attrAny.setTextContent(yourXMLFragment.trim());
attr.getAttributeValues().add(attrAny);
attributeStatement.getAttributes().add(attr);
Actually this above does not yeld correct results. The above example can be used only to create xsany with text content not xml content (xml content gets escaped).
So after digging in opensaml sources the following did work as needed:
public XSAny createXSAny(Element dom)
{
XSAnyBuilder anyBuilder = (XSAnyBuilder) Configuration.getBuilderFactory().getBuilder(XSAny.TYPE_NAME);
XSAny any = anyBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSAny.TYPE_NAME);
// this builds only the root element not the whole dom
XSAny xo=anyBuilder.buildObject(dom);
// set/populate dom so whole dom gets into picture
xo.setDOM(dom);
any.getUnknownXMLObjects().add(xo);
return any;
}

Categories

Resources