Implémentation de l’interface IURIDereferencer

Adobe AIR 1.5 et les versions ultérieures

Pour valider une signature XML, vous devez fournir une implémentation de l’interface IURIDereferencer. Cette implémentation est chargée de résoudre les URI spécifiées dans les éléments Reference d’un document de signature XML, puis de renvoyer les données pour que le digest puisse être calculé. Le digest calculé est comparé au digest de la signature pour déterminer si les données référencées ont été modifiées depuis la création de la signature.

Remarque : les applications AIR de type HTML doivent importer une bibliothèque SWF contenant une implémentation ActionScript pour valider les signatures XML. L’interface IURIDereferencer ne peut pas être implémentée en JavaScript.

L’interface IURIDerefencer possède une méthode unique, dereference(uri:String) , qui doit être implémentée. L’objet XMLSignatureValidator appelle cette méthode pour chaque référence présente dans la signature. La méthode doit renvoyer les données dans un objet ByteArray.

Dans la plupart des cas, vous devrez également ajouter des propriétés ou des méthodes permettant à votre objet déréférenceur de localiser les données référencées. Par exemple, si les données signées sont dans le même document que la signature, vous pouvez ajouter une variable de membre fournissant une référence au document XML. La méthode dereference() peut alors utiliser cette variable, ainsi que l’URI, pour localiser les données référencées. De la même façon, si les données signées sont situées dans un répertoire du système de fichiers local, la méthode dereference() peut avoir besoin d’une propriété fournissant le chemin conduisant à ce répertoire pour résoudre les fichiers référencés.

L’objet XMLSignatureValidator repose entièrement sur l’objet déréférenceur pour interpréter les chaînes URI. Les règles standard du déréférencement des URI sont données dans la section 4.3.3 de la recommandation du W3C portant sur la syntaxe et le traitement des signatures XML.

Déréférencement des URI dans les signatures enveloppées

Lorsqu’une signature XML enveloppée est générée, ses éléments sont insérés dans les données signées. Par exemple, si vous signez le message suivant à l’aide d’une structure de signature enveloppée :

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

Le document signé résultant ressemblera à cela :

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

Remarquez que la signature contient un élément Reference avec une chaîne vide pour son URI. Dans ce contexte, une chaîne vide fait référence à la racine du document.

Notez également que l’algorithme de transformation spécifie qu’une transformation de signature enveloppée a été appliquée. Lorsqu’une telle transformation a été appliquée, l’objet XMLSignatureValidator supprime automatiquement la signature du document avant de calculer le digest. Cela signifie que le déréférenceur n’a pas besoin de supprimer l’élément Signature lorsqu’il renvoie les données.

L’exemple suivant présente un déréférenceur pour signatures enveloppées :

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

Cette classe de déréférenceur utilise une fonction constructeur avec un paramètre, signedMessage , pour mettre le document de signature enveloppée à la disposition de la méthode dereference() . Comme la référence placée dans une signature enveloppée fait toujours référence à la racine des données signées, la méthode dereferencer() écrit le document dans un tableau d’octets et renvoie celui-ci.

Déréférencement des URI dans des signatures enveloppantes et détachées

Lorsque les données signées sont dans le même document que la signature elle-même, les URI des références utilisent généralement la syntaxe XPath ou XPointer pour traiter les éléments signés. La recommandation du W3C portant sur la syntaxe et le traitement des signatures XML ne conseillant que cette syntaxe, il est préférable de baser votre implémentation sur les signatures que vous envisagez de rencontrer (et d’ajouter suffisamment de vérification d’erreur pour traiter intelligemment la syntaxe non prise en charge).

La signature d’une application AIR est un exemple de signature enveloppante. Les fichiers de l’application sont répertoriés dans un élément Manifest. L’élément Manifest est traité dans l’attribut Reference URI à l’aide de la chaîne, « #PackageContents », qui fait référence à l’identifiant de l’élément 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>

Pour valider cette signature, un déréférenceur doit prendre la chaîne URI conteneur, « #PackageContents » provenant de l’élément Reference, puis renvoyer l’élément Manifest dans un objet ByteArray. Le symbole « # » fait référence à la valeur d’un attribut Id d’élément.

L’exemple suivant implémente un déréférenceur pour valider des signatures d’application AIR. Pour rester simple, l’implémentation s’appuie sur la structure connue d’une signature AIR. Un déréférenceur d’objectif général pourrait être beaucoup plus complexe.

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

Lorsque vous vérifiez ce type de signature, seules les données de l’élément Manifest sont validées. Les fichiers stockés dans le package ne sont absolument pas vérifiés. Pour vérifier l’altération éventuelle des fichiers du package, vous devez lire ces fichiers, calculer leur digest SHA256 et comparer le résultat au digest enregistré dans l’élément Manifest. L’objet XMLSignatureValidator ne vérifie pas automatiquement de telles références secondaires.

Remarque : cet exemple n’est fourni que pour illustrer le processus de validation d’une signature. Il n’a que peu d’utilité dans une application AIR validant sa propre signature. Si l’application a déjà été modifiée, l’agent de modification peut simplement supprimer la vérification de la validation.

Calcul des valeurs digest pour des ressources externes

AIR ne comprend pas de fonction intégrée pour le calcul des digests SHA256, mais le kit SDK Flex comprend une classe d’utilitaires SHA256. Le kit SDK comprend également la classe d’utilitaires encodeurs Base64 très utile pour comparer le digest calculé au digest stocké dans une signature.

L’exemple de fonction suivant lit et valide les fichiers d’un manifeste de package 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; 
}

La fonction exécute une boucle sur toutes les références de l’élément Manifest. Pour chaque référence, le digest SHA256 est calculé, encodé au format base64, puis comparé au digest du manifeste. Les URI d’un package AIR font référence aux chemins relatifs vers le répertoire de l’application. Les chemins sont résolus en fonction de l’emplacement du fichier de signature, toujours situé dans le sous-répertoire META-INF du répertoire de l’application. Notez que la classe Flex SHA256 renvoie le digest sous la forme d’une chaîne de caractères hexadécimale. Cette chaîne doit être convertie en ByteArray contenant les octets représentés par la chaîne hexadécimale.

Pour utiliser les classes mx.utils.SHA256 et Base64Encoder dans Flash CS4, vous pouvez localiser et copier ces classes dans le répertoire de développement de votre application ou compiler une bibliothèque SWF contenant les classes à l’aide du kit SDK Flex.

Déréférencement des URI dans des signatures détachées référençant des données externes

Lorsqu’une URI fait référence à une ressource externe, les données doivent être accédées et chargées dans un objet ByteArray. Si l’URI contient une URL absolue, il s’agit simplement de lire un fichier ou de demander une URL. Si, comme dans la plupart des cas, l’URI contient un chemin relatif, votre implémentation IURIDereferencer doit permettre de résoudre les chemins conduisant aux fichiers signés.

L’exemple suivant utilise un objet File initialisé lorsque l’occurrence de dereferencer est construite en tant que base de résolution des fichiers signés.

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

La fonction dereference() localise simplement le fichier visé par l’URI de référence, charge le contenu du fichier dans un tableau d’octets, puis renvoie cet objet ByteArray.

Remarque : avant de valider des références externes à distance, voyez si votre application peut être vulnérable à une attaque de téléphone personnel ou du même type par un document de signature créé à des fins malveillantes.