當需要建立數位簽章並傳送給同儕進行驗證時,通常會使用 XML 正規化。由於數位簽章是根據 XML 資料建立的,因此 XML 資料必須先經過正規化,才能計算其簽章值。即使是一個額外的空格也可能會影響計算出的簽章值,因此必須遵循一些規則來正規化 XML 資料,使其具有標準格式。這就是為什麼 W3C 制定了 Canonical XML Version 1.1 規範的原因。
此規範提供了格式化元素節點、屬性節點和命名空間節點等的規則。不同的程式語言都實作了此規範,因此我們可以輕鬆地使用它們來正規化 XML 資料,而無需了解規範的詳細資訊。
在本教學中,我們不會深入探討規範的細節,我們只會專注於如何呼叫 Java API 來正規化 XML 資料。根據 XPath 資料模型,XML 文件是由一組節點(節點集)表示的。而 Java 中的 XML API 接收一個要正規化的 Data 物件。為了示範正規化,我們將建立一個自訂的 NodeSetDataImpl,它實作了 NodeSetData 和 Iterator 介面。
import java.util.Iterator;
import javax.xml.crypto.NodeSetData;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.NodeIterator;
public class NodeSetDataImpl implements NodeSetData, Iterator {
private Node ivNode;
private NodeFilter ivNodeFilter;
private Document ivDocument;
private DocumentTraversal ivDocumentTraversal;
private NodeIterator ivNodeIterator;
private Node ivNextNode;
public NodeSetDataImpl(Node pNode, NodeFilter pNodeFilter) throws Exception {
ivNode = pNode;
ivNodeFilter = pNodeFilter;
if (ivNode instanceof Document) {
ivDocument = (Document) ivNode;
} else {
ivDocument = ivNode.getOwnerDocument();
}
ivDocumentTraversal = (DocumentTraversal) ivDocument;
}
private NodeSetDataImpl(NodeIterator pNodeIterator) {
ivNodeIterator = pNodeIterator;
}
public Iterator iterator() {
NodeIterator nodeIterator = ivDocumentTraversal.createNodeIterator(ivNode, NodeFilter.SHOW_ALL, ivNodeFilter, false);
return new NodeSetDataImpl(nodeIterator);
}
private Node checkNextNode() {
if (ivNextNode == null && ivNodeIterator != null) {
ivNextNode = ivNodeIterator.nextNode();
if (ivNextNode == null) {
ivNodeIterator.detach();
ivNodeIterator = null;
}
}
return ivNextNode;
}
private Node consumeNextNode() {
Node nextNode = checkNextNode();
ivNextNode = null;
return nextNode;
}
public boolean hasNext() {
return checkNextNode() != null;
}
public Node next() {
return consumeNextNode();
}
public void remove() {
throw new UnsupportedOperationException("Removing nodes is not supported.");
}
public static NodeFilter getRootNodeFilter() {
return new NodeFilter() {
public short acceptNode(Node pNode) {
if (pNode instanceof Element && pNode.getParentNode() instanceof Document) {
return NodeFilter.FILTER_SKIP;
}
return NodeFilter.FILTER_ACCEPT;
}
};
}
}
然後我們需要建立一個 CanonicalizationMethod 來執行實際的正規化。這裡有一些選擇:
CanonicalizationMethod.INCLUSIVE CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS CanonicalizationMethod.EXCLUSIVE CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS
在本教學中,我們只會示範包含式正規化。以下是執行此操作的程式碼。
import java.io.ByteArrayInputStream;
import javax.xml.crypto.Data;
import javax.xml.crypto.OctetStreamData;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
public class CanonicalizationTest {
private static String INPUT =
"" +
"hello" +
"world" +
"";
public static void main(String[] args){
transform(INPUT);
}
/**
* Create new document with data
*
* @param xml
* @return
*/
private static Document createNewDocument(String xml){
try{
byte[] bytes = xml.getBytes("UTF-8");
ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
DocumentBuilderFactory fac = DocumentBuilderFactory.newInstance();
fac.setNamespaceAware(true);
DocumentBuilder docBuilder = fac.newDocumentBuilder();
Document doc = docBuilder.parse(bin);
return doc;
} catch (Exception ex){
ex.printStackTrace();
}
return null;
}
/**
* Transform a XML string
*
* @param xml
*/
private static void transform(String xml){
Document doc = createNewDocument(xml);
try{
Data data = new NodeSetDataImpl(doc, NodeSetDataImpl.getRootNodeFilter());
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
CanonicalizationMethod canonicalizationMethod = fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec)null);
// Doing the actual canonicalization
OctetStreamData transformedData =(OctetStreamData) canonicalizationMethod.transform(data, null);
byte[] bytes = tool.Util.readStream(transformedData.getOctetStream());
String str = new String(bytes);
System.out.println(str);
} catch (Exception ex){
ex.printStackTrace();
}
}
}
首先,讓我們看一下輸出:
<my:Node xmlns:my="http://example.com" Id="1">hello</my:Node><my:Node xmlns:my="http://example.com" Id="2">world</my:Node>
測試會先建立一個 Document,然後將此 Document 傳遞給 NodeSetDataImpl,以建立要正規化的節點集。從輸出中,我們可以看到 Root 節點在正規化的資料中被移除,這是因為 NodeSetDataImpl 中的 NodeFilter 過濾了此節點。接下來,第二個 my:Node 在正規化形式中,xmlns:my 節點位於 Id 節點之前。這是基於 Canonical XML 規範,其中節點應按詞彙順序排列。
對於其他正規化方法,這裡的邏輯是相同的。不同之處在於輸出不相同,因為不同的正規化方法有不同的規則。
No comment for this article.