Implementowanie interfejsu IURIDereferencer

Adobe AIR 1.5 i starsze wersje

Aby zweryfikować podpis XML, należy udostępnić implementację interfejsu IURIDereferencer. Implementacja odpowiada za tłumaczenie identyfikatorów URI w elementach Reference dokumentu podpisu XML i za zwrócenie danych umożliwiających obliczenie wyciągu. Obliczony wyciąg jest porównywany z wyciągiem zapisanym w podpisie w celu określenia, czy dane będące celem odwołania, zostały zmodyfikowane po utworzeniu podpisu.

Uwaga: Aplikacje AIR oparte na kodzie HTML, aby mogły weryfikować podpisy XML, muszą importować bibliotekę SWF zawierającą implementację w języku ActionScript. Interfejsu IURIDereferencer nie można zaimplementować w języku JavaScript.

Interfejs IURIDerefencer zawiera jedną metodę, dereference(uri:String) , którą należy zaimplementować. Obiekt XMLSignatureValidator wywołuje tę metodę dla każdego odwołania w podpisie. Metoda musi zwracać dane w obiekcie ByteArray.

W większości przypadków konieczne będzie także dodanie właściwości lub metod umożliwiających obiektowi tłumaczącemu odwołania odnalezienie danych będących celem odwołań. Na przykład, jeśli podpisane dane znajdują się w tym samym dokumencie, co podpis, można dodać zmienną składową udostępniającą odwołanie do dokumentu XML. Metoda dereference() może następnie na podstawie tej zmiennej i identyfikatora URI odszukać dane będące celem odwołania. Podobnie, jeśli podpisane dane znajdują się w katalogu lokalnego systemu plików, metoda dereference() może korzystać z właściwości udostępniającej ścieżkę do tego katalogu, a tym samym pozwalającej na zlokalizowanie plików będących celem odwołania.

Klasa XMLSignatureValidator do interpretacji identyfikatorów URI wykorzystuje wyłącznie funkcję dereference. Standardowe reguły tłumaczenia odwołań w postaci identyfikatorów URI są podane w sekcji 4.3.3 zaleceń W3C dotyczących składni i przetwarzania podpisów XML.

Tłumaczenie identyfikatorów URI w podpisach w kopercie

Podczas generowania podpisu XML w kopercie elementy podpisu są wstawiane do podpisywanych danych. Na przykład, jeśli podpiszemy następującą wiadomość przy użyciu struktury podpisu w kopercie:

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

...wynikowy podpisany dokument będzie miał następującą postać:

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

Należy zwrócić uwagę, że podpis zawiera pojedynczy element Reference z pustym ciągiem znaków jako identyfikatorem URI. Pusty ciąg znaków w tym kontekście oznacza początek dokumentu.

Należy także zwrócić uwagę, że algorytm transformacji wymusza zastosowanie transformacji podpisu w kopercie. Zastosowanie transformacji podpisu w kopercie powoduje, że obiekt XMLSignatureValidator automatycznie usuwa podpis z dokumentu przed obliczeniem wyciągu. Oznacza to, że obiekt tłumaczący odwołania nie musi usuwać elementu Signature ze zwracanych danych:

Poniższy przykład ilustruje klasę tłumaczącą odwołania dla podpisów w kopercie:

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

Klasa tłumacząca odwołania ma konstruktor z parametrem signedMessage , który udostępnia podpis w kopercie metodzie dereference() . Ponieważ odwołanie w podpisie w kopercie zawsze wskazuje na początek podpisywanych danych, metoda dereferencer() zapisuje dokument w tablicy bajtów i zwraca tę tablicę.

Tłumaczenie identyfikatorów URI w podpisach typu koperta i odłączonych.

Gdy podpisane dane są umieszczone w tym samym dokumencie, co sam podpis, identyfikatory URI w odwołaniach zazwyczaj zbudowane są zgodnie ze składnią XPath lub XPointer, adresując podpisane elementy. W zaleceniach W3C dotyczące składni i przetwarzania podpisów XML składnia ta nie jest traktowana jako obowiązkowa, lecz jako zalecana, dlatego własną implementację należy opracować pod kątem podpisów, jakie najprawdopodobniej napotka aplikacja (i uzupełnić o mechanizmy obsługi błędów prawidłowo reagujące na nieobsługiwaną składnię).

Przykładem podpisu typu koperta jest podpis aplikacji AIR. Pliki wchodzące w skład aplikacji są wymienione w elemencie Manifest. Adres elementu Manifest jest podany w atrybucie URI elementu Reference w postaci ciągu znaków „#PackageContents”, który odwołuje się do identyfikatora elementu Manifest:

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

Obiekt tłumaczący odwołania podczas weryfikacji tego podpisu musi pobrać identyfikator URI zawierający ciąg "#PackageContents" z elementu Reference i zwrócić element Manifest w obiekcie ByteArray. Symbol „#” oznacza wartość atrybutu Id elementu.

W poniższym przykładzie zaimplementowano klasę tłumaczącą odwołania na potrzeby weryfikacji podpisów aplikacji AIR. Implementacja jest prosta, ponieważ bazuje na znanej strukturze podpisu aplikacji AIR. Uniwersalna klasa tłumacząca odwołania byłaby znacznie bardziej skomplikowania.

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

Podczas weryfikacji podpisu tego typu sprawdzane są wyłącznie dane w elemencie Manifest. Faktyczne pliki w pakiecie nie są w ogóle weryfikowane. Aby sprawdzić, czy pliki w pakiecie nie zostały zmodyfikowane, należy je wczytać, obliczyć wyciąg SHA256 i porównać wynik z wyciągiem zarejestrowanym w manifeście. Klasa XMLSignatureValidator nie sprawdza automatycznie takich odwołań drugiego poziomu.

Uwaga: Przedstawiony przykład ma za zadanie jedynie zilustrować proces weryfikacji podpisu. W praktyce nie ma większego sensu weryfikowanie podpisu aplikacji AIR wewnątrz niej samej. Jeśli aplikacja została już w sposób nieuprawniony zmodyfikowana, to modyfikacja ta mogłaby po prostu polegać na usunięciu procedury weryfikacji.

Obliczanie wartości wyciągów dla zasobów zewnętrznych

Środowisko AIR nie zawiera wbudowanych funkcji do obliczania wyciągów SHA256, ale pakiet Flex SDK zawiera klasę narzędziową SHA256. Pakiet SDK zawiera ponadto klasę narzędziową kodera Base64, przydatną przy porównywaniu obliczonego wyciągu z wyciągiem zapisanym w podpisie.

Poniższa przykładowa funkcja odczytuje i weryfikuje pliki z wymienione w manifeście pakietu środowiska 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; 
}

Funkcja zawiera pętlę przeglądającą wszystkie odwołania w elemencie Manifest. Dla każdego odwołania obliczany jest wyciąg SHA256, który następnie zostaje zakodowany w formacie base64 i porównany z wyciągiem w manifeście. Identyfikatory URI w pakiecie środowiska AIR wskazują na ścieżki określone względem katalogu aplikacji. Ścieżki są tłumaczone na podstawie położenia pliku podpisu, który zawsze znajduje się w podkatalogu META-INF wewnątrz katalogu aplikacji. Należy zwrócić uwagę, że klasa SHA256 środowiska Flex zwraca wyciąg jako ciąg cyfr szesnastkowych. Konieczne jest przekształcenie tego ciągu w obiekt ByteArray zawierający bajty opisane przez ciąg szesnastkowy.

Aby skorzystać z klas mx.utils.SHA256 i Base64Encoder w programie Flash CS4, można odszukać i skopiować te klasy do katalogu, w którym tworzona jest aplikacja, lub przy użyciu pakietu Flex SDK skompilować plik SWF biblioteki zawierający klasy.

Tłumaczenie identyfikatorów URI w podpisach odłączonych odwołujących się do danych zewnętrznych

Gdy identyfikator URI odwołuje się do zasobu zewnętrznego, konieczne jest uzyskanie dostępu do tych danych i załadowanie ich do obiektu ByteArray. Jeśli identyfikator URI zawiera bezwzględny adres URL, operacja ta sprowadza się do wczytania pliku lub zażądania danych spod adresu URL. Jeśli, co zdarza się częściej, identyfikator URI zawiera ścieżkę względną, implementacja interfejsu IURIDereferencer musi zawierać mechanizm tłumaczący ścieżki na bezwzględne położenia podpisanych plików.

W poniższym przykładzie użyto obiektu File inicjowanego podczas konstruowania instancji klasy tłumaczącej odwołania. Obiekt ten stanowi podstawę do tłumaczenia położeń podpisanych plików.

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

Funkcja dereference() znajduje plik adresowany przez identyfikator URI odwołania, ładuje treść pliku do tablicy bajtów i zwraca obiekt ByteArray.

Uwaga: Przed podjęciem decyzji o weryfikacji odwołań zewnętrznych należy rozważyć ewentualną podatność aplikacji na ataki typu „telefon do domu” (skierowanie aplikacji do systemu atakującego) lub podobne, realizowane przy użyciu odpowiednio spreparowanego dokumentu podpisu.