實作 IURIDereferencer 介面

Adobe AIR 1.5 以及更新的版本

若要驗證 XML 簽名,您必須提供 IURIDereferencer 介面的實作。這份實作應負責對 XML 簽名文件中 Reference 元素的 URI 進行解析,然後傳回資料以便計算摘要。算出的摘要會拿來與簽名中的摘要進行比較,以判斷參照的資料自簽名建立後是否曾遭到竄改。

備註: HTML 類型的 AIR 應用程式必須匯入一個含有 ActionScript 實作的 SWF 元件庫,以驗證 XML 簽名。IURIDereferencer 介面無法以 JavaScript 進行實作。

IURIDerefencer 介面只具有一個方法,就是 dereference(uri:String) ,而此方法必須實作。XMLSignatureValidator 物件會針對簽名中的各個參照來呼叫這個方法。此方法必須以 ByteArray 物件傳回資料。

在大部分的情況下,您也必須新增某些屬性或方法,讓您的提領者 (dereferencer) 物件可以找到參照的資料。例如,如果簽署的資料與簽名位於同一份文件內,您就可以新增一個成員變數來參照該 XML 文件。 dereference() 方法接著便可使用這個變數搭配 URI 來找到參照的資料。同樣地,如果簽署的資料位於本機檔案系統中的某個目錄, dereference() 方法可能就需要一個屬性來提供該目錄的路徑,這樣才能夠解析參照的檔案。

XMLSignatureValidator 完全依賴提領者來解譯 URI 字串。URI 的標準提領規則在 W3C 建議事項的「XML 簽章語法及處理」(XML Signature Syntax and Processing) 的第 4.3.3 節有說明。

提領封內簽名中的 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 語法來敘述已簽署的元素。W3C 建議事項的「XML 簽章語法及處理」(XML-Signature Syntax and Processing) 只建議使用此語法,所以您應該根據將處理的簽名來進行實作 (並且增加充分的錯誤檢查作業,以從容地處理未受支援的語法)。

AIR 應用程式的簽名就是封外簽名的一個例子。應用程式中的檔案列於 Manifest 元素內。Manifest 元素是在 Reference URI 特質中以「#PackageContents」字串描述,而此字串參照 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 字串取出,然後以 ByteArray 物件傳回 Manifest 元素。「#」符號是指元素的 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 類別,您可以找出這些類別並將它們複製到應用程式開發目錄中,也可以編譯一個文件庫 SWF,其中含有使用 Flex SDK 的類別。

向參照外部資料的分離簽名提領 URI

當 URI 參照外部資源時,必須存取資料就並將其載入 ByteArray 物件。如果 URI 含有的是絕對 URL,那麼就只是要讀取檔案或要求 URL 的問題而已。如果 URI 含有的是相對 URL (這可能是比較常見的情形),您的 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) 或類似類型的攻擊。