Canonicalize XML in Java

  sonic0002        2016-01-20 01:39:45       18,987        0          English  简体中文  繁体中文  ภาษาไทย  Tiếng Việt 

當需要建立數位簽章並傳送給同儕進行驗證時,通常會使用 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 規範,其中節點應按詞彙順序排列。

對於其他正規化方法,這裡的邏輯是相同的。不同之處在於輸出不相同,因為不同的正規化方法有不同的規則。

JAVA  JAVA SECURITY  XML 

       

  RELATED


  0 COMMENT


No comment for this article.



  RANDOM FUN

Google at work