转换通过允许您在生成引用的数据的哈希值之前修改该数据,使您可以对已签名的内容进行更多的控制。例如,信封式签名转换在对xml文档进行签名之前会移除Signature节点。引用可以指定任何数量的转换,这些转换按照在Transforms元素中指定的顺序而做用。.NET Framework中的类除了支持我们前面提到的信封式签名转换以外,还支持下列转换:
XPath表达式被指定为Transform元素下的XPath元素的文本内容。需要注意的是,XPath转换充当筛选器,而不是充当在作为输入传递的xml中选择节点的手段。该转换针对作为输入传递给该转换的每个节点计算XPath表达式,结果被转换为布尔值。输入节点将被考虑以便传递计算,并且如果计算的结果为true,则输入节点将被包含在转换的输出中。考虑转换的以下xml输入:
Some data
Even more data
假设我们只希望选择“b”节点进行签名。带有XPath表达式“ancestor-or-self::b”的XPath转换将返回以下节点集(它正是我们所需要的):
Some data
Even more data
该XPath表达式的Transform元素看起来类似于下面的代码片段:
>
要在签名时以编程方式创建转换,请创建某个转换对象的实例,适当设置它的属性,并且将其添加到它所应用于的引用。以下示例将上一个示例中使用的XPath转换添加到某个引用中,以便创建转换:
// Add an XPath transform to a reference.
// Assume a Reference object in refr
xmlDocument doc = new xmlDocument();
doc.Loadxml("ancestor-or-self::b");
xmlDsigXPathTransform xptrans = new xmlDsigXPathTransform();
xptrans.LoadInnerxml(doc.ChildNodes);
refr.AddTransform(xptrans);
规范化的目的是为两个逻辑上相同但可能不是由相同的文本表示的xml片段产生相同的xml数据。例如,请观察下面两个代码片段。它们在逻辑上是相同的;它们的不同之处仅仅在于文本表示。但是如果您要对它们按原样进行哈希运算,则得到的哈希值将是不同的:
Some text
Some text
要避免该问题,该标准中指定的默认规范化算法将执行很多任务,包括消除开始和结束标记中的空白以及将空元素转换为开始/结束标记对。但是,它不会更改元素内容中的任何空白。所执行操作的完整列表可以在Canonical xml(位于http://www.w3.org/TR/xml-c14n)中得到。
签名引擎在必要时自动规范化数据,以便符合W3C标准。特别地,每当签名引擎需要将xml数据转换为二进制数据以便进行哈希运算时,都会规范化该数据。例如,当它准备对SignedInfo元素及其子元素进行签名时,就会发生这种情况。当它准备引用或转换的输出以便进行签名时,也可能发生这种情况。例如,如果您使用基于ID的引用(指向包含签名的文档中的其他xml数据),并且该引用没有与其相关联的转换,则签名引擎在对该引用的xml数据进行哈希运算之前将规范化该数据。
xml签名标准提供了KeyInfo元素,帮助进行密钥管理。该元素可以存储密钥名称、密钥值、密钥检索方法或证书信息,以帮助接收方验证签名。该标准没有指定应当如何信任以及是否应当信任KeyInfo元素中的任何信息。如果发送方和接收方共享一个受信任密钥列表,或者如果您发现了其他某种用于将密钥名称映射到密钥的方法,则KeyInfo元素可能很有用。.NET Framework 1.x具有一些对密钥名称、值和检索方法的支持,.NET Framework 2.0还包含对X.509证书的支持。让我们假设发送方和接收方共享一个密钥列表,接收方对于他期望从其接收消息的每个发送方都具有一个公钥。签名应用程序可以添加以下代码,以便向签名中添加KeyInfo元素:
// Adds an KeyInfo element with RSA public key information to the
// signature.
// Assumes a Signedxml object in sig, and an RSA object in key.
KeyInfo ki = new KeyInfo();
ki.AddClause(new RSAKeyValue(key));
sig.KeyInfo = ki;
该代码应当在调用ComputeSignature之前添加。它将在签名中产生一个KeyInfo元素,该元素看起来类似于以下代码:
这表示用来对xml文档进行签名的RSA公钥。接收方应用程序应当将该密钥与受信任密钥列表进行比较,如果该公钥不在列表中,则不应当信任文档。否则,攻击者就可以在传输过程中替换已经签名的文档,并且用另外一个密钥对其进行签名。如果签名包含与此类似的RSAKeyValue,则验证代码可以调用Signedxml类的CheckSignature方法(它不采用任何参数),并且.NET Framework将根据RSAKeyValue元素计算出该密钥。下面是一个示例:
// Verify a signature that includes RSAKeyInfo or DSAKeyInfo.
// Assume a Signedxml object in sig.
bool verified = sig.CheckSignature();
伴随xml签名的灵活性而来的是一定数量的风险。因为转换是如此灵活,所以可能很难精确计算出签名涵盖了哪些数据,这可能导致意外的或不安全的结果。这些签名配置文件可以通过指定应用程序所支持的签名形式,在该方面提供帮助。尽管没有相应于签名配置文件的标准,但签名配置文件起码应当指定应用程序期望签名具有的引用和转换,以便您可以确保所期望签名的数据确实进行了签名。签名配置文件还可以包含其他数据,例如,期望签名数据具有的签名算法或密钥大小。应用程序应当检查并强制它所创建和验证的那些签名符合该应用程序所支持的签名配置文件。要更好地理解配置文件为什么如此重要,请考虑图1。假设您要编写接受xml签名数据的应用程序,但是您的应用程序只期望使用信封式签名转换而非任何其他转换的签名。现在,有人向您发送了带有额外XPath转换的签名文档,如图6所示。
Hello
World
signature"/>
>
图6 使用额外的转换对文档进行签名
在图6的示例中,签名只涵盖示例文档中的“a”元素。如果您刚刚加载了该文档并且调用了Signedxml类的CheckSignature方法,则即使“b”元素未被该签名涵盖,该签名仍然可能验证,这是因为签名引擎将应用在该签名中指定的转换。如果应用程序依赖于该签名涵盖了“b”元素这一前提,则数据的完整性已经遭到损害。应用程序应当检验只有一个引用具有作为URI的空字符串并且该引用具有一个转换——信封式签名,从而验证它所期望的签名配置文件。它会在验证签名时拒绝任何其他签名配置文件。一些用于检验该签名配置文件的示例代码显示在图7中。
// This method checks the signature profile for the signature
// in the supplied document. It ensures there is only one
// Signature element and only one enveloped reference with only
// one enveloped signature transform
public bool CheckSignatureProfile(xmlDocument doc)
{
// Make sure there is only one Signature element
xmlNamespaceManager nsm = new xmlNamespaceManager(new NameTable());
nsm.AddNamespace("dsig", Signedxml.xmlDsigNamespaceUrl);
xmlNodeList sigList = doc.SelectNodes("//dsig:Signature", nsm);
if (sigList.Count > 1)
return false; //Wrong number of Signature elements
//Make sure the Signature element has only one Reference
xmlElement sigElt = (xmlElement)sigList[0];
xmlNodeList refList = sigElt.SelectNodes(
"dsig:SignedInfo/dsig:Reference", nsm);
if (refList.Count > 1)
return false; //Wrong number of Reference elements
// Make sure the Reference URI is ""
xmlElement refElt = (xmlElement)refList[0];
xmlAttributeCollection refAttrs = refElt.Attributes;
xmlNode uriAttr = refAttrs.GetNamedItem("URI");
if ((uriAttr == null) || (uriAttr.Value != ""))
return false; // Wrong type of reference
// Make sure the only tranform is the enveloped signature transform
xmlNodeList transList = refElt.SelectNodes(
"dsig:Transforms/dsig:Transform", nsm);
if (transList.Count != 1)
return false; //Wrong number of Transform elements
xmlElement transElt = (xmlElement)transList[0];
string transAlg = transElt.GetAttribute("Algorithm");
if (transAlg != Signedxml.xmlDsigEnvelopedSignatureTransformUrl)
return false; //Wrong type of transform
return true;
}
图7 检验一个签名配置文件