Implementación de la interfaz IURIDereferencer

Adobe AIR 1.5 y posterior

Para validar una firma XML, debe proporcionar una implementación de la interfaz IURIDereferencer. La implementación es responsable de resolver los URI en los elementos Reference de un documento de firma XML y devolver los datos para que se pueda calcular el resumen. El resumen calculado se compara con el resumen en la firma para determinar si los datos a los que se hace referencia se han modificado desde que se creó la firma.

Nota: las aplicaciones de AIR basadas en HTML deben importar una biblioteca SWF que contenga una implementación de ActionScript para poder validar firmas XML. La interfaz IURIDereferencer no se puede implementar en JavaScript.

La interfaz IURIDerefencer tiene un solo método, dereference(uri:String) , que debe implementarse. El objeto XMLSignatureValidator llama a este método para todas las referencias de la firma. El método debe devolver los datos en un objeto ByteArray.

En la mayoría de los casos, también será necesario agregar propiedades o métodos que permitan al objeto dereferencer localizar los datos a los que se hace referencia. Por ejemplo, si los datos firmados se ubican en el mismo documento que la firma, puede añadir una variable miembro que proporcione una referencia al documento XML. El método dereference() puede utilizar después esta variable, junto con el URI, para localizar los datos a los que se hace referencia. Del mismo modo, si los datos firmados se encuentran en un directorio del sistema de archivos local, el método dereference() puede necesitar una propiedad que proporcione la ruta al directorio para poder resolver los archivos a los que se hace referencia.

XMLSignatureValidator confía por completo en el objeto dereferencer para interpretar las cadenas de URI. Las reglas estándar para eliminar las referencias de los URI se incluyen en la sección 4.3.3 de la recomendación del W3C para el procesamiento y la sintaxis de la firma XML.

Eliminación de referencias de los URI en firmas protegidas

Cuando se genera una firma XML protegida, sus elementos se insertan en los datos firmados. Por ejemplo,si se ha firmado el siguiente mensaje utilizando una estructura de firma protegida:

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

El documento firmado resultante presentará el siguiente aspecto:

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

Se debe tener en cuenta que la firma contiene un solo elemento Reference con una cadena vacía como su URI. Una cadena vacía en este contexto hace referencia a la raíz del documento.

También se debe observar que el algoritmo de transformación especifica que se ha aplicado una transformación de firma protegida. Cuando se aplica una transformación de este tipo, XMLSignatureValidator elimina automáticamente la firma del documento antes de calcular el resumen. Esto significa que el objeto dereferencer no necesita eliminar el elemento Signature al devolver los datos.

El siguiente ejemplo muestra un elemento dereferencer para firmas protegidas:

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

Esta clase dereferencer utiliza una función constructora con un parámetro, signedMessage , para que el documento de firma protegida esté disponible en el método dereference() . Debido a que la referencia en una firma protegida siempre hace referencia a la raíz de los datos firmados, el método dereferencer() escribe el documento en un conjunto de bytes y lo devuelve.

Eliminación de referencias de los URI en firmas con protección y separadas

Si los datos firmados se ubican en el mismo documento que la propia firma, los URI de las referencias suelen utilizar la sintaxis XPath o XPointer para hacer referencia a los elementos que están firmados. La recomendación W3C para el procesamiento y la sintaxis de la firma XML únicamente recomienda esta sintaxis, por lo que la implementación se debe basar en las firmas que se esperan encontrar (y añadir una comprobación de errores suficiente para administrar correctamente la sintaxis no admitida).

La firma de una aplicación de AIR es un ejemplo de una firma con protección. Los archivos de la aplicación se incluyen en un elemento Manifest. Al elemento Manifest se hace referencia en el atributo URI Reference utilizando la cadena “#PackageContents”, que hace referencia al Id. del 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>

Una clase dereferencer para validar esta firma debe tomar la cadena URI que contiene "#PackageContents" del elemento Reference y devolver el elemento Manifest en un objeto ByteArray. El símbolo “#” hace referencia al valor de un atributo Id. del elemento.

El siguiente ejemplo implementa un elemento dereferencer para validar firmas de la aplicación de AIR. La implementación constituye un proceso sencillo al confiar en la estructura conocida de una firma de AIR. Un objeto dereferencer de uso general podría resultar mucho más complejo.

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

Cuando se verifica este tipo de firma, únicamente se validan los datos del elemento Manifest. Los archivos reales del paquete no se comprueban. Para comprobar modificaciones en los archivos del paquete, debe leer los archivos, calcular el resumen SHA256 y comparar el resultado con el resumen registrado en el manifiesto. XMLSignatureValidator no comprueba automáticamente estas referencias secundarias.

Nota: este ejemplo se incluye para mostrar el proceso de validación de firma. No resulta muy útil en una aplicación de AIR que valida su propia firma. Si la aplicación ya se ha manipulado, el agente de las alteraciones podría eliminar simplemente la comprobación de validación.

Cálculo de valores de resumen para recursos externos

AIR no incluye funciones incorporadas para calcular resúmenes SHA256, pero el SDK de Flex incluye una clase de utilidad SHA256. El SDK también incluye la clase de utilidad del codificador Base64 que resulta útil para comparar el resumen calculado con el resumen almacenado en una firma.

La siguiente función de ejemplo lee y valida los archivos en un manifiesto de un paquete de 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 función recorre todas las referencias en el elemento Manifest. Para cada referencia, se calcula el resumen SHA256, codificado en formato base64, y se compara con el resumen en el manifiesto. Los URI de un paquete de AIR hacen referencia a las rutas relativas al directorio de la aplicación. Las rutas se resuelven en función de la ubicación del archivo de firma, que siempre está en el subdirectorio META-INF del directorio de la aplicación. Se debe tener en cuenta que la clase SHA256 de Flex devuelve el resumen como una cadena de caracteres hexadecimales. Esta cadena se debe convertir en un objeto ByteArray que contenga los bytes representados mediante la cadena hexadecimal.

Para utilizar las clases mx.utils.SHA256 y Base64Encoder en Flash CS4, puede ubicar y copiar estas clases en el directorio de desarrollo de la aplicación o compilar un archivo SWF de biblioteca que incluya las clases utilizando el SDK de Flex.

Eliminación de referencia de los URI en firmas separadas que hacen referencia a datos externos

Cuando un URI hace referencia a un recurso externo, se debe acceder a los datos y estos deben cargarse en un objeto ByteArray. Si el URI contiene una dirección URL absoluta, simplemente se debe leer un archivo o solicitar una URL. Probablemente el caso más común es que si el URI se contiene en una ruta relativa, la implementación de IURIDereferencer debe incluir un modo de resolver las rutas a los archivos firmados.

El siguiente ejemplo utiliza un objeto File inicializado cuando la instancia de dereferencer se crea como base para resolver los archivos firmados.

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 función dereference() simplemente ubica el archivo al que se dirige el URI de referencia, carga el contenido del archivo en un conjunto de bytes y devuelve el objeto ByteArray.

Nota: antes de validar las referencias externas remotas, tenga en cuenta si la aplicación podría ser vulnerable a un ataque de tipo “phone home” o ataque similar por parte de un documento de firma creado de forma malintencionada.