IURIDereferencer インターフェイスの実装

Adobe AIR 1.5 およびそれ以降

XML 署名を検証するためには、IURIDereferencer インターフェイスの実装を用意する必要があります。この実装は、XML 署名ドキュメントの Reference エレメントに含まれる URI を解決してデータを返し、ダイジェストを計算できるようにします。計算されたダイジェストは署名内のダイジェストと比較され、それにより、署名の作成後に参照先のデータが変更されたかどうかを確認できます。

注意: HTML ベースの AIR アプリケーションで XML 署名を検証するためには、ActionScript の実装を含む SWF ライブラリをインポートする必要があります。IURIDereferencer インターフェイスは JavaScript では実装できません。

IURIDerefencer インターフェイスには、 dereference(uri:String) という 1 つのメソッドがあります。このメソッドは実装する必要があります。XMLSignatureValidator オブジェクトは、署名内の各参照に対してこのメソッドを呼び出します。このメソッドは、ByteArray オブジェクトでデータを返す必要があります。

ほとんどの場合、間接参照オブジェクトが参照先データを見つけられるようにするためのプロパティやメソッドも追加する必要があります。例えば、署名されたデータが署名と同じドキュメント内にある場合は、その XML ドキュメントへの参照を提供するメンバ変数を追加します。 dereference() メソッドは、この変数を URI と共に使用して、参照先のデータを見つけることができます。同様に、署名されたデータがローカルファイルシステムのディレクトリにある場合、 dereference() メソッドが参照先ファイルを解決するためには、そのディレクトリへのパスを提供するプロパティが必要です。

XMLSignatureValidator は、URI ストリングの解釈を間接参照に完全に依存します。URI の間接参照に関する標準的な規則は、W3C 勧告「XML Signature Syntax and Processing」のセクション 4.3.3 に記載されています。

enveloped 署名での URI の間接参照

enveloped XML 署名が生成されるときは、署名対象データの内部に署名エレメントが挿入されます。例えば、enveloped 署名構造を使用して次のメッセージに署名したとします。

<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>

この署名には、空のストリングを URI として含む Reference エレメントが 1 つあります。このコンテキストでの空のストリングは、ドキュメントのルートを指します。

また、変換アルゴリズムは、enveloped 署名変換が適用されていることを示しています。 enveloped 署名変換が適用された場合、XMLSignatureValidator は、ダイジェストを計算する前にドキュメントから署名を自動的に削除します。つまり、間接参照がデータを返すときに Signature エレメントを削除する必要はありません。

次の例に、enveloped 署名の間接参照を示します。

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 というパラメーターを持つコンストラクター関数を使用して、enveloped 署名ドキュメントを dereference() メソッドで使用できるようにします。enveloped 署名内の参照は常に署名対象データのルートを指すので、 dereferencer() メソッドはドキュメントをバイト配列に書き込み、それを返します。

enveloping 署名および detached 署名での URI の間接参照

署名対象のデータが署名自体と同じドキュメント内にある場合、通常、参照の URI は XPath シンタックスまたは XPointer シンタックスを使用して署名対象エレメントのアドレスを指定します。W3C 勧告「XML Signature Syntax and Processing」はこのシンタックスを推奨しているだけなので、想定される署名に基づいて実装を行う必要があります(さらに、サポートされないシンタックスを適切に処理するための十分なエラーチェックを追加する必要があります)。

AIR アプリケーションの署名は、enveloping 署名の一例です。アプリケーション内のファイルは Manifest エレメントに一覧表示されます。Manifest エレメントのアドレスは、Reference URI 属性内で、Manifest エレメントの ID を指す “#PackageContents” というストリングを使用して指定されます。

<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 クラスは、ダイジェストを 16 進文字のストリングとして返します。このストリングを、16 進ストリングで表されたバイトを含む ByteArray に変換する必要があります。

Flash CS4 で mx.utils.SHA256 クラスと Base64Encoder クラスを使用するには、これらのクラスを探してアプリケーション開発ディレクトリにコピーするか、これらのクラスを含む SWF ライブラリを Flex SDK を使用してコンパイルします。

外部データを参照する detached 署名での 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」攻撃(情報を収集して密かに攻撃者のサイトへと送信すること)または似た種類の攻撃に対してアプリケーションが脆弱性を持っていないか、事前に検討してください。