实现 IURIDereferencer 接口

Adobe AIR 1.5 和更高版本

要验证 XML 签名,必须提供 IURIDereferencer 接口的实现。此实现负责解析 XML 签名文档的 Reference 元素中的 URI 并返回该数据,以便可以计算摘要。计算的摘要与签名中的摘要进行比较,以确定自创建签名以来引用的数据是否被更改。

注: 基于 HTML 的 AIR 应用程序必须导入包含 ActionScript 实现的 SWF 库才能验证 XML 签名。在 JavaScript 中无法实现 IURIDereferencer 接口。

IURIDerefencer 接口有一个必须实现的单一方法 dereference(uri:String) 。XMLSignatureValidator 对象对签名中的每个引用调用此方法。此方法必须将数据返回 ByteArray 对象。

在大多数情况下,还需要添加允许取消引用程序对象定位被引用数据的属性或方法。例如,如果签名数据与签名位于同一文档中,则可以添加成员变量来提供对 XML 文档的引用。然后, dereference() 方法可以使用此变量和 URI 定位被引用的数据。同样,如果签名数据位于本地文件系统的目录中,则 dereference() 方法可能需要一个提供该目录路径的属性才能解析被引用的文件。

XMLSignatureValidator 完全依赖取消引用程序来解释 URI 字符串。“W3C Recommendation for XML Signature Syntax and Processing”(W3C 推荐的 XML 签名语法和处理标准)的第 4.3.3 节中给出了取消引用 URI 的标准规则。

在封装的签名中取消引用 URI

当生成封装的 XML 签名时,签名元素将被插入签名的数据中。例如,如果使用封装的签名结构对以下消息进行了签名,

<message> 
    <data>...</data> 
</message>

结果签名的文档将如下所示:

<message> 
    <data>...</data> 
     <Signature xmlns="http://www.w3.org/2000/09/xmldsig#"> 
        <SignedInfo> 
            <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/> 
            <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/> 
            <Reference URI=""> 
                <Transforms> 
                    <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/> 
                </Transforms> 
                <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/> 
                <DigestValue>yv6...Z0Y=</DigestValue> 
            </Reference> 
        </SignedInfo> 
        <SignatureValue>cCY...LQ==</SignatureValue> 
        <KeyInfo> 
            <X509Data> 
                <X509Certificate>MII...4e</X509Certificate> 
            </X509Data> 
        </KeyInfo> 
    </Signature> 
</message>

注意,该签名包含单一的 Reference 元素,并使用空字符串作为其 URI。此上下文中的空字符串引用文档的根。

还应注意,转换算法指定应用了封装的签名转换。当应用了封装的签名转换后,XMLSignatureValidator 在计算简要之前将自动从文档中删除签名。这意味着在返回数据时取消引用程序不需要删除 Signature 元素。

以下示例阐释了用于封装的签名的取消引用程序:

package 
{ 
    import flash.events.ErrorEvent; 
      import flash.events.EventDispatcher;  
    import flash.security.IURIDereferencer; 
    import flash.utils.ByteArray; 
    import flash.utils.IDataInput; 
 
    public class EnvelopedDereferencer 
        extends EventDispatcher implements IURIDereferencer 
    { 
        private var signedMessage:XML; 
         
        public function EnvelopedDereferencer( signedMessage:XML ) 
        { 
            this.signedMessage = signedMessage; 
        } 
 
        public function dereference( uri:String ):IDataInput 
        { 
            try 
            { 
                if( uri.length != 0 ) 
                { 
                    throw( new Error("Unsupported signature type.") ); 
                } 
                var data:ByteArray = new ByteArray(); 
                data.writeUTFBytes( signedMessage.toXMLString() ); 
                data.position = 0; 
            } 
            catch (e:Error) 
                { 
                var error:ErrorEvent = 
                    new ErrorEvent("Ref error " + uri + " ", false, false, e.message); 
                this.dispatchEvent(error); 
                data = null; 
                throw new Error("Reference not resolvable: " + uri + ", " + e.message); 
            } 
            finally 
            { 
                return data; 
            } 
        }         
    } 
}

此取消引用程序类使用构造函数和参数 signedMessage 将封装的签名文档提供给 dereference() 方法。由于封装的签名中的引用始终引用签名的数据的根,因此 dereferencer() 方法可以将文档写入字节数组并将其返回。

在封装签名和断开的签名中取消引用 URI

当签名的数据与签名本身位于同一文档时,引用中的 URI 通常使用 XPath 或 XPointer 语法对签名的元素寻址。针对 XML 签名语法和处理的 W3C 建议仅建议使用此语法,因此应将您的实现基于预期会遇到的签名(并添加足够的错误检查来妥善地处理不支持的语法)。

AIR 应用程序的签名是封装签名的一个示例。该应用程序中的文件列在 Manifest 元素中。Manifest 元素使用字符串“#PackageContents”存入 Reference URI 属性中,该字符串引用 Manifest 元素的 Id:

<Signature xmlns="http://www.w3.org/2000/09/xmldsig#" Id="PackageSignature"> 
    <SignedInfo> 
        <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/> 
        <SignatureMethod Algorithm="http://www.w3.org/TR/xmldsig-core#rsa-sha1"/> 
        <Reference URI="#PackageContents"> 
            <Transforms> 
                <Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/> 
            </Transforms> 
            <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/> 
            <DigestValue>ZMGqQdaRKQc1HirIRsDpeBDlaElS+pPotdziIAyAYDk=</DigestValue> 
        </Reference> 
    </SignedInfo> 
    <SignatureValue Id="PackageSignatureValue">cQK...7Zg==</SignatureValue> 
    <KeyInfo> 
        <X509Data> 
            <X509Certificate>MII...T4e</X509Certificate> 
        </X509Data> 
    </KeyInfo> 
    <Object> 
    <Manifest Id="PackageContents"> 
        <Reference URI="mimetype"> 
            <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"> 
            </DigestMethod> 
            <DigestValue>0/oCb84THKMagtI0Dy0KogEu92TegdesqRr/clXct1c=</DigestValue> 
        </Reference> 
        <Reference URI="META-INF/AIR/application.xml"> 
            <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"> 
            </DigestMethod> 
            <DigestValue>P9MqtqSdqcqnFgeoHCJysLQu4PmbUW2JdAnc1WLq8h4=</DigestValue> 
        </Reference> 
        <Reference URI="XMLSignatureValidation.swf"> 
            <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"> 
            </DigestMethod> 
            <DigestValue>OliRHRAgc9qt3Dk0m0Bi53Ur5ur3fAweIFwju74rFgE=</DigestValue> 
        </Reference> 
    </Manifest> 
</Object> 
</Signature>

用于验证此签名的取消引用程序必须从 Reference 元素获取包含 "#PackageContents" 的 URI 字符串,并将 Manifest 元素返回到 ByteArray 对象中。“#”符号引用元素 ID 属性的值。

以下示例实现用于验证 AIR 应用程序签名的取消引用程序。此实现通过依赖于 AIR 签名的已知结构而保持了简单的结构。通用的取消引用程序要复杂得多。

package 
{ 
    import flash.events.ErrorEvent;    
    import flash.security.IURIDereferencer; 
    import flash.utils.ByteArray; 
    import flash.utils.IDataInput; 
     
    public class AIRSignatureDereferencer implements IURIDereferencer { 
        private const XML_SIG_NS:Namespace = 
            new Namespace( "http://www.w3.org/2000/09/xmldsig#" ); 
        private var airSignature:XML; 
     
        public function AIRSignatureDereferencer( airSignature:XML ) { 
            this.airSignature = airSignature; 
        } 
         
        public function dereference( uri:String ):IDataInput {     
            var data:ByteArray = null;     
            try 
            {     
                if( uri != "#PackageContents" ) 
                { 
                    throw( new Error("Unsupported signature type.") ); 
                } 
                var manifest:XMLList = 
                    airSignature.XML_SIG_NS::Object.XML_SIG_NS::Manifest; 
                data = new ByteArray(); 
                data.writeUTFBytes( manifest.toXMLString()); 
                data.position = 0; 
            } 
            catch (e:Error) 
            { 
                data = null; 
                throw new Error("Reference not resolvable: " + uri + ", " + e.message); 
            } 
            finally 
            { 
                return data; 
            } 
             
        } 
    } 
}

当验证此类型的签名时,仅验证 Manifest 元素中的数据。根本未检查包中的实际文件。要检查包文件是否被篡改,必须读取此文件,计算 SHA256 摘要,并将结果与清单中记录的摘要进行比较。XMLSignatureValidator 不自动检查此类二级引用。

注: 提供本例仅为了阐释签名验证过程。在验证自签名的 AIR 应用程序中很少使用。如果应用程序已被篡改,则篡改代理只需删除该验证检查。

计算外部资源的摘要值

AIR 不包含用于计算 SHA256 摘要的内置函数,但 Flex SDK 包含一个 SHA256 实用程序类。SDK 还包含 Base64 编辑器实用程序类,该类在比较计算的摘要与签名中存储的摘要时非常有用。

以下示例函数读取并验证 AIR 包清单中的文件。

import mx.utils.Base64Encoder; 
import mx.utils.SHA256; 
 
private function verifyManifest( sigFile:File, manifest:XML ):Boolean 
{ 
    var result:Boolean = true; 
    var message:String = ''; 
    var nameSpace:Namespace = manifest.namespace(); 
     
    if( manifest.nameSpace::Reference.length() <= 0 ) 
    { 
        result = false; 
        message = "Nothing to validate."; 
    } 
    for each (var reference:XML in manifest.nameSpace::Reference) 
    { 
        var file:File = sigFile.parent.parent.resolvePath( reference.@URI ); 
        var stream:FileStream = new FileStream(); 
        stream.open(file, FileMode.READ); 
        var fileData:ByteArray = new ByteArray(); 
        stream.readBytes( fileData, 0, stream.bytesAvailable ); 
 
        var digestHex:String = SHA256.computeDigest( fileData ); 
        //Convert hexidecimal string to byte array 
        var digest:ByteArray = new ByteArray(); 
        for( var c:int = 0; c < digestHex.length; c += 2 ){ 
            var byteChar:String = digestHex.charAt(c) + digestHex.charAt(c+1); 
            digest.writeByte( parseInt( byteChar, 16 )); 
        } 
        digest.position = 0; 
         
        var base64Encoder:Base64Encoder = new Base64Encoder(); 
        base64Encoder.insertNewLines = false; 
        base64Encoder.encodeBytes( digest, 0, digest.bytesAvailable ); 
        var digestBase64:String = base64Encoder.toString(); 
        if( digestBase64 == reference.nameSpace::DigestValue ) 
        { 
            result = result && true; 
            message += "   " + reference.@URI + " verified.\n"; 
        } 
        else 
        { 
            result = false; 
            message += " ---- " + reference.@URI + " has been modified!\n"; 
        } 
        base64Encoder.reset(); 
    } 
    trace( message ); 
    return result; 
}

该函数循环访问 Manifest 元素中的所有引用。对于每个引用,将计算 SHA256 摘要,用 base64 格式对其编码,并与清单中的摘要进行比较。AIR 包中的 URI 引用相对于应用程序目录的路径。基于签名文件的位置解析该路径,签名文件始终位于应用程序目录的 META-INF 子目录中。注意,Flex SHA256 类返回以十六进制字符表示的摘要字符串。此字符串必须转换为 ByteArray,其中包含由十六进制字符串表示的字节。

若要在 Flash CS4 中使用 mx.utils.SHA256 和 Base64Encoder 类,可以找到这些类并将其复制到您的应用程序开发目录中,或者使用 Flex SDK 编译包含这些类的库 SWF。

在引用外部数据的断开签名中取消引用 URI

当 URI 引用外部资源时,必须访问该数据并将其加载到 ByteArray 对象中。如果 URI 包含绝对 URL,则只需读取一个文件或请求一个 URL。如果 URI 包含到相对路径中,则 IURIDereferencer 实现必须包含一种方法,能够将该路径解析到签名文件中,这可能是较常见的情形。

以下示例使用构建取消引用程序实例时初始化的 File 对象作为解析签名文件的基础。

package 
{ 
    import flash.events.ErrorEvent; 
    import flash.events.EventDispatcher; 
    import flash.filesystem.File; 
    import flash.filesystem.FileMode; 
    import flash.filesystem.FileStream; 
    import flash.security.IURIDereferencer; 
    import flash.utils.ByteArray; 
    import flash.utils.IDataInput; 
    public class RelativeFileDereferencer 
        extends EventDispatcher implements IURIDereferencer 
    { 
        private var base:File; 
         
        public function RelativeFileDereferencer( base:File ) 
        { 
            this.base = base; 
        } 
 
        public function dereference( uri:String ):IDataInput 
        { 
            var data:ByteArray = null; 
            try{ 
                var referent:File = this.base.resolvePath( uri ); 
                var refStream:FileStream = new FileStream();             
                data = new ByteArray(); 
                refStream.open( referent, FileMode.READ ); 
                 
                refStream.readBytes( data, 0, data.bytesAvailable ); 
                 
            } catch ( e:Error ) { 
                data = null; 
                throw new Error("Reference not resolvable: " + referent.nativePath + ", " + e.message ); 
            } finally { 
                return data; 
            } 
        }         
    } 
}

dereference() 函数找出由引用 URI 寻址的文件,将文件内容加载到字节数组中,并返回 ByteArray 对象。

注: 验证远程外部引用之前,应该考虑您的应用程序是否容易遭受恶意制造的签名文档所发起的“打电话回家”(phone home) 攻击或类似攻击。