Implementazione dell'interfaccia IURIDereferencer

Adobe AIR 1.5 e versioni successive

Per convalidare una firma XML, dovete fornire un'implementazione dell'interfaccia IURIDereferencer. L'implementazione si occupa della risoluzione degli URI contenuti negli elementi Reference di un documento della firma XML e della restituzione dei dati che consentono il calcolo del digest. Il digest calcolato viene confrontato con quello contenuto nella firma, per determinare se i dati a cui viene fatto riferimento sono stati modificati dopo la creazione della firma.

Nota: le applicazioni AIR basate su HTML devono importare una libreria SWF contenente un'implementazione di ActionScript per convalidare le firme XML. L'interfaccia IURIDereferencer non può essere implementata in JavaScript.

L'interfaccia IURIDerefencer dispone di un unico metodo, dereference(uri:String) , che deve essere implementato. L'oggetto XMLSignatureValidator chiama questo metodo per ogni riferimento presente nella firma. Il metodo deve restituire i dati in un oggetto ByteArray.

Nella maggior parte dei casi, dovete aggiungere anche le proprietà o i metodi che consentano all'oggetto dereferencer di individuare i dati a cui viene fatto riferimento. Se, ad esempio, i dati firmati si trovano nello stesso documento con la firma, potreste aggiungere una variabile membro che fornisca un riferimento al documento XML. Il metodo dereference() può quindi usare questa variabile, insieme all'URI, per individuare i dati a cui si fa riferimento. In modo analogo, se i dati firmati si trovano in una directory del file system locale, il metodo dereference() potrebbe richiedere una proprietà che fornisca il percorso di tale directory per risolvere i file a cui si fa riferimento.

XMLSignatureValidator si basa completamento su dereferencer per interpretare le stringhe degli URI. Le regole standard per annullare i riferimenti agli URI sono indicate alla sezione 4.3.3 della W3C Recommendation for XML Signature Syntax and Processing (Raccomandazione W3C per la sintassi e l'elaborazione delle firme XML).

Annullamento dei riferimenti agli URI nelle firme enveloped

Quando viene generata una firma XML enveloped, gli elementi della firma vengono inseriti nei dati firmati. Se, ad esempio, avete firmato il seguente messaggio usando una struttura di firma enveloped:

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

Il documento firmato avrà il seguente aspetto:

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

Come potete notare, la firma contiene un unico elemento Reference con una stringa vuota come URI. Una stringa vuota in questo contesto si riferisce alla radice del documento.

Anche l'algoritmo di trasformazione specifica che è stata applicata una trasformazione della firma enveloped. In questo caso, XMLSignatureValidator rimuove automaticamente la firma dal documento prima di calcolare il digest. Ciò significa che il dereferencer non deve rimuovere l'elemento Signature quando restituisce i dati.

Nel seguente esempio viene illustrato un dereferencer per le firme di tipo 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; 
            } 
        }         
    } 
}

Questa classe dereferencer usa una funzione di costruzione con un parametro, signedMessage , per rendere disponibile il documento con la firma enveloped al metodo dereference() . Poiché il riferimento a una firma enveloped si riferisce sempre alla radice dei dati firmati, il metodo dereferencer() scrive il documento in un array di byte e lo restituisce.

Annullamento dei riferimenti agli URI nelle firme di tipo enveloping e detached

Quando i dati firmati si trovano nello stesso documento della firma, gli URI nei riferimenti usano di solito la sintassi XPath o XPointer per l'indirizzamento degli elementi firmati. La W3C Recommendation for XML Signature Syntax and Processing consiglia solo questa sintassi, quindi è opportuno basare la vostra implementazione sulle firme che prevedete di utilizzare (aggiungendo un livello di controllo errori sufficiente per gestire opportunamente la sintassi non supportata).

La firma di un'applicazione AIR è un esempio di firma enveloping. I file dell'applicazione sono elencati in un elemento Manifest. L'indirizzo dell'elemento Manifest è incluso nell'attributo Reference URI tramite la stringa “#PackageContents”, che fa riferimento all'ID dell'elemento 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>

Un dereferencer per la convalida di questa firma deve rilevare la stringa URI contenente "#PackageContents" dall'elemento Reference e restituire l'elemento Manifest in un oggetto ByteArray. Il simbolo “#” si riferisce al valore dell'attributo ID di un elemento.

Nel seguente esempio viene implementato un dereferencer per la convalida delle firme di un'applicazione AIR. Per mantenere semplice l'implementazione, viene usata una struttura nota di una firma AIR. Un dereferencer generico può essere significativamente più complesso.

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

Quando verificate questo tipo di firma, vengono convalidati solo i dati nell'elemento Manifest. I file effettivi contenuti nel pacchetto non vengono controllati. Per verificare che i file contenuti nel pacchetto non siano stati manipolati, dovete leggere i file, calcolare il digest SHA256 e confrontare il risultato con il digest registrato nel manifesto. XMLSignatureValidator non controlla automaticamente questi riferimenti secondari.

Nota: questo esempio viene fornito solo per illustrare il processo di convalida delle firme. È infatti inutile che un'applicazione AIR convalidi la propria firma. Se l'applicazione è già stata manipolata, l'agente che ha effettuato questa operazione potrebbe avere semplicemente rimosso il controllo di convalida.

Calcolo dei valori del digest per le risorse esterne

In AIR non sono disponibili funzioni incorporate per il calcolo dei digest SHA256, mentre in Flex SDK è inclusa una classe di utilità SHA256. Nell'SDK è inoltre inclusa una classe di utilità Base64Encoder utile per il confronto del digest calcolato con quello archiviato in una firma.

La seguente funzione di esempio legge e convalida i file nel manifesto di un pacchetto 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; 
}

Questa funzione scorre tutti i riferimenti presenti nell'elemento Manifest. Per ogni riferimento, viene calcolato il digest SHA256, codificato in formato Base64 e confrontato con il digest nel manifesto. Gli URI in un pacchetto AIR fanno riferimento ai percorsi relativi alla directory dell'applicazione. I percorsi vengono risolti in base alla posizione del file della firma, che si trova sempre nella sottodirectory META-INF della directory dell'applicazione. La classe Flex SHA256 restituisce il digest in un formato stringa di caratteri esadecimali. Questa stringa deve essere convertita in un oggetto ByteArray contenente i byte rappresentati dalla stringa esadecimale.

Per usare le classi mx.utils.SHA256 e Base64Encoder in Flash CS4, potete individuarle e copiarle nella directory di sviluppo della vostra applicazione o compilare una libreria SWF contenente le classi usando Flex SDK.

Annullamento dei riferimenti agli URI nelle firme di tipo detached che fanno riferimento ai dati esterni

Quando un URI fa riferimento a una risorsa esterna, è necessario accedere ai dati e caricarli in un oggetto ByteArray. Se l'URI contiene un URL assoluto, si tratta solo di leggere un file o richiedere un URL. Se l'URI contiene un riferimento a un percorso relativo, che è probabilmente il caso più comune, la vostra implementazione di IURIDereferencer deve includere un metodo per risolvere i percorsi dei file firmati.

Nell'esempio seguente viene usato un oggetto File che viene inizializzato quando l'istanza dereferencer viene costruita come base per la risoluzione dei file firmati.

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 funzione dereference() individua semplicemente il file a cui si riferisce l'URI, carica il contenuto del file in un array di byte e restituisce l'oggetto ByteArray.

Nota: prima di convalidare riferimenti esterni remoti, determinare se l'applicazione può essere vulnerabile ad attacchi di tipo "phone home" o altri attacchi simili eseguiti con documenti di firma contenenti codice dannoso.