使用Java验证LotusFormsXML数字签名

Lotus Forms XML 数字签名简介

本文关注在不依赖 Lotus Forms API 的情况下,使用 JSR 105 API 对签名的 Lotus Forms 文档进行验证(可下载源 代码,见 下载 小节)。这种方法简化了使用标准 Java™ API 调用和来自 其他供应商的 XML 签名实现,将从其他供应商购买的产品与 Lotus Forms 集成 的过程。

对于本文而言,表单是指包含使用 Extensible Forms Description Language (XFDL) 描述的标记的 XML 文档,Extensible Forms Description Language (XFDL) 是用于描述 Lotus Forms 文档的、使用标准 XML 格式的词汇表。在 XFDL 表单内,用户界面控件称为项,项使用 XML 元素和属性 编码。项通常被绑定到 XForms 实例中的数据,并且被组织为一个或多个可视页 面。

XML 签名被包含在 XFDL 文档内,因此它们是被封装的签名。根据 Model-View-Controller (MVC) 概念,一个 XML 数字签名属于 XForms 实例中的 数据模型的一部分,因为数字签名的生成是一种信息创建行为,由具有私有密匙 的用户完成。生成签名后,对 XFDL 文档的任何修改都将造成签名验证失败。XML 签名还可以有选择地忽略被签名文档的各部分,这样多步骤工作流就可以对这些 被忽略的部分执行操作,并且不会影响到数字签名的有效性。这种安排的两个优 点就是允许对表单使用多步骤签名,以及签名之间可以彼此重叠。

清单 1 展示了 XForms 模型中的 XML 签名的例子。注意 xforms、ds 和 dsxp 的名称 空间 URI 在表单的 文档元素中定义,这里并没有显示。完整的表 单示例可以在 下载 小节找到。

清单 1. 包含未进行签名的 XML 签名的 XForms 实例                 John      M      Smith      123456789      1000 Main Street      La      California      10080      M      1980-01-02      1001234567      1001236789                                                                  /xfdl:XFDL/xfdl:globalpage/          xfdl:global/xfdl:xformsmodels/xforms:model[1]/          xforms:instance [@id="Generated"]/data/page1/signature1/           ds:Signature                                                                                                         here()/ancesTor::             ds:Signature[1]/ds:Object [sigmeta.:metadata]                                                                                                                                                                      

XML 签名包含一个表示签名内容的 Reference 元素。Reference 不具有 URI 属性,在 XFDL 文档中这表示整个文档都应当被签名隐藏。Reference 然后包含 一个 Transform,可以去掉生成的 XML 签名。这一步是必需的,因为 XML 签名 被封装在已签名的 XFDL 文档的内部。当所引用的资源(XFDL 文档)的数字指纹 被计算后,Reference 的 DigestValue 元素将为空。计算结果随后被存储到 DigestValue 中,这将修改 XFDL 文档。然而,实际上没有发生修改(从 XML 文 档中去除 XML 签名),这就是我们从对其计算摘要的内容中去掉已封装签名的原 因。

在清单 1 中,SignedInfo 中还有一个 Reference。XML 签名可以对创建者所 需要的任意数量的资源进行签名。在本例中,我们使用它对生成的 XML 签名的额 外元数据进行签名。例如,这一特性可用于在生成的 XML 签名中包含一个简单的 XAdES 时间戳。

图 1. 样例签名表单

验证签名表单

查看应用程序(比如 IBM Lotus Forms Viewer)或程序可以使用一个私有密 匙对表单进行签名。下载 小节提供了一个未进行签名的样例表单。建议用户下载 Lotus Forms Viewer 来对表单进行签名并理解其工作原理。IBM Lotus Forms Server 还使用户能够对表单进行数字签名,不需要下载和安装 Lotus Forms Viewer。

除 Lotus Forms API 以外,还可以使用标准的 Java XML Digital Signature API(在 JSR 105 中定义)对签名表单进行交叉验证,这个 API 也作为 Java 6 的一部分包含在其中。

要断言某个表单没有被篡改,必须对表单中的所有签名进行验证。这种验证可 以通过查找所有包含 Signature 元素和非空 SignatureValue 元素的 XForms 实 例来完成。对于此类 XForms 实例中的每一个,将针对验证创建一个单独的、具 有合适名称空间的实例文档。出于性能考虑,可以使用用于 XML 的流 API 来拉 取 XForms 实例并放入到一个新的 Document Object Model (DOM) 文档中。

准备好 DOM 实例文档后,从要进行验证的 Signature DOM 节点中创建一个 DOMValidateContext。还需要用一个 KeySelecTor 从 KeyInfo 部分的签名证书 中提取公共密匙。DOMValidateContext 使用前面小节中的定制 URI dereferencer 来执行 XML 标准化(canonization)。解组 DOMValidateContext 将创建一个 XMLSignature 对象,其 validate() 方法可以返回表示签名是否有 效的信息。

对所有 XForms 实例中的所有签名进行验证后,将收集最终生成的结果,表示 完整表单的验证状态。

处理 XForms 数据模型

如前一小节所述,Lotus Forms 中的 XML 签名是一个封装的签名,包含 XForms 实例的标记。要准确地验证此类签名,必须为 XForms 实例数据构建 DOM 。W3C XForms 规范(http://www.w3.org/TR/xforms/#structure-model- instance)要求将实例数据提取到一个单独的 DOM 文档中,并且如果实例数据被 内联到文档内部,那么必须从 XForms 实例元素及其祖先节点继承相应的名称空 间。所有对数据的操作应当在单独的 DOM 文档中完成。

这种方法意味着要创建文档,必须从包含 XML 签名的 xform.:instance 内容 中克隆 DOM 子树。新的文档应当包含注释和 PI 节点,以及来自 xforms:instance 的惟一子元素。对于与清单 1 相对应的已签名表单,必须使用 从所有祖先节点继承的名称空间创建一个新文档,其中包含 文档 元素。这个文档被称为实例文档,而源文档被称为表单文档。

清单 2. 提取的文档元素的名称空间

… 

由于父节点的名称空间前缀可以被一个子节点覆盖,比如默认名称空间,因此 如果名称空间已被复制到目标,那么就不会再次复制该名称空间。这种方法遵守 名称空间覆盖规则,因此只有距离最近的名称空间定义是有效的。

清单 3. 将名称空间从源节点的所有祖先复制到目标节点的代码

private void cloneNamespaces(Element src, Element target)  {   HashMap copiedNamespaces = new HashMap();   Node node = src;   while (node != null && node.getNodeType() ==  Node.ELEMENT_NODE) {     NamedNodeMap attributes = node.getAttributes();     for (int i = 0; i < attributes.getLength(); i++)  {       Attr attr = (Attr) attributes.item(i);       String namespaceUri = attr.getNamespaceURI();       if (!XML_NS_URI.equals(namespaceUri)) {         // a normal attribute, don't copy it          continue;       }       String nodeValue = attr.getNodeValue();       String localName = attr.getLocalName();       String prefix = attr.getPrefix();       String qualifiedName = (prefix == null) ?  localName : prefix           + ":" + localName; //$NON-NLS-1$       // don't overwrite namespace already copied.       if (copiedNamespaces.containsKey(qualifiedName)) {         continue;       }       Document facTory = target.getOwnerDocument();       Attr newAttr = facTory.createAttributeNS (namespaceUri,           qualifiedName);       newAttr.setNodeValue(nodeValue);       target.setAttributeNodeNS(newAttr);       copiedNamespaces.put(qualifiedName, nodeValue);     }     node = node.getParentNode();   }}

定制 URI dereferencer

由于 XForms 处理模型要求将包含签名的实例数据从源表单文档(本例中为 XFDL 文档)中单独实例化,因此带有 URI=”” 的引用(相同的文档引用)表示对 实例文档的开始部分的引用,而不是完整的 XFDL 文档。我们希望能够对整个 XFDL 文档进行签名,而不是仅仅是数据,从而在签名双方创建安全的协议和合约 。

标准 XML 签名允许一个特殊的 Reference:不带有 URI 属性 Reference。 XML 签名规范将其定义为接收方应用程序应当能够识别在这种情况下应当使用的 对象。在 Lotus Forms 中,未包含 URI 的 Reference 表示整个 XFDL 文档。因 此,需要一个定制 dereferencer 来知道如何从各种引用的 URI 中获取所有被引 用数据(包括不带 URI 的引用)。这种定制 dereferencer 实现了 javax.xml.crypto.URIDereferencer 并且被称为 NoUriDereferencer。

NoUriDereferencer 使用委托(delegation)模式构建。在取消引用期间,如 果 URIReference 对象的 URI 属性为 null,那么表单文档被作为八位字节流 (octet stream)返回(尽管返回 XML 文档节点集会更加有效,要实现除相同文 档引用以外的引用,javax.xml.crypto.URIDereference> 文档必须使用八位 字节流)。对于所有其他 URI,类将委托由标准 JSR 105 实现提供的最初的默认 dereferencer。

清单 4. Dereferencer 代码

public static class  NoUriDereferencer implements URIDereferencer{   private InputStream inputStream;   public NoUriDereferencer(InputStream inputStream) throws  IOException   {     this.inputStream = inputStream;   }   public Data dereference(URIReference uriReference,  XMLCryptoContext context)     throws URIReferenceException   {     if (uriReference.getURI() == null)     {       Data data = new OctetStreamData (this.inputStream);       return data;     }     else     {       URIDereferencer defaultDereferencer =  XMLSignatureFacTory.getInstance("DOM").         getURIDereferencer();       return defaultDereferencer.dereference(uriReference,  context);     }   }}

验证证书

对于 Lotus Forms 中的已签署签名,证书链被放入到 Signature 元素的 KeyInfo 中,其中第一个证书为已签名证书。证书链可由 Java Certification Path API 进行检验。此外,如果维护了可信的接受方名称列表,那么可以比较已 签名证书的接受方,看看它是否匹配可信列表中的接受方。这一步骤可以确保表 单的签名者是可信的。

分步运行样例代码

所需的库

Java 1.4 或 5 需要 JSR 105 实现。本文使用了 Apache XML Security Java 库。由于 Java XML Digital Signature API 已经被包含到 JDK 6 中,因此 Java 6 不需要任何额外的 JAR 文件。

针对 Java 6 的说明

从本文的下载小节下载包含所有样例代码和表单的 ZIP 文件

将文件解压缩到一个目录中,比如 /forms

切换到目录 /forms/xmldsig/bin: cd /forms/xmldsig/bin

确保使用了正确的 Java 版本:java -version

运行样例代码和表单:java xmldsig.FormVerification ../src/test/resource/form-signed.xfdl

一个已经发现的问题:在撰写本文时,IBM JDK6 中的 XMLDSig 实现有一个名 称空间标准化 bug,并且不支持 here() 函数,因此无法使用它对样例表单进行 验证。

针对 Java 1.4 或 5 的说明

从本文的下载小节下载包含所有样例代码和表单的 ZIP 文件

将文件解压缩到一个目录中,比如 /forms

从 http://xml.apache.org/security/dist/java-library/ 下载 Apache XML Security 库 xml-security-bin-1_4_2.zip

将其解压缩到 /forms(可以在随后的表单中看到目录 xml-security-1_4_2)

如果使用来自 Sun 的 Java 1.4 的话,将 xalan.jar、xml-apis.jar、 serializer.jar 从 /forms/xml-security-1_4_2/libs 复制到 jre/lib/endorsed(参见 http://santuario.apache.org/Java/installation.html)

切换到目录 /forms/xmldsig/bin: cd /forms/xmldsig/bin

确保使用了正确的 Java 版本:java -version

运行样例代码和表单:java -classpath .;../../xml-security- 1_4_2/libs/xmlsec-1.4.2.jar;../../xml-security-1_4_2/libs/commons- logging.jar;../../xml-security-1_4_2/libs/xalan.jar xmldsig.FormVerification ../src/test/resource/form-signed.xfdl

结束语

Lotus Forms 使用针对 XForms 和 XML Signature 的 W3C 标准来表示它的数 字签名。因此,可以根据开放标准轻松地对表单进行验证,从而实现将其集成到 企业应用程序流程中。

本文配套源码

人生就像是一场旅行,遇到的既有感人的,

使用Java验证LotusFormsXML数字签名

相关文章:

你感兴趣的文章:

标签云: