Implementação da interface IURIDereferencer

Adobe AIR 1.5 e posterior

Para validar uma assinatura XML, você deve fornecer uma implementação da interface IURIDereferencer. A implementação é responsável por resolver as URIs dentro dos elementos Reference de um documento de assinatura XML e retornar os dados de forma que seja possível computar a compilação. A compilação computada é comparada com a compilação da assinatura para determinar se os dados referenciados foram alterados desde a criação da assinatura.

Nota: Aplicativos do AIT baseados em HTML devem importar uma biblioteca SWF contendo uma implementação ActionScript para validar assinaturas XML. Não é possível implementar a interface IURIDereferencer em JavaScript.

A interface IURIDerefencer tem um único método, dereference(uri:String) , que deve ser implementado. O objeto XMLSignatureValidator chama esse método para cada referência na assinatura. O método deve retornar os dados em um objeto ByteArray.

Na maioria dos casos, você também precisará adicionar propriedades ou métodos que permitam cancelar referência ao objeto para localizar os dados referenciados. Por exemplo, se os dados assinados estiverem localizados no mesmo documento da assinatura, você pode adicionar uma variável membro que forneça uma referência ao documento XML. O método dereference() pode usar essa variável, juntamente com a URI, para localizar os dados referenciados. Da mesma forma, se os dados assinados estiverem localizados em um diretório do sistema de arquivos locais, o método dereference() poderá precisar de uma propriedade que forneça o caminho para o diretório a fim de resolver os arquivos referenciados.

O XMLSignatureValidator conta inteiramente com o cancelador de referências para interpretar as sequências de caracteres da URI. As regras padrão para cancelar referências de URIs são apresentadas na seção 4.3.3 das Recomendações do W3C para processamento e sintaxe de assinatura XML.

Cancelamento de referência de URIs em assinaturas envelopadas

Quando uma assinatura XML envelopada é gerada, os elementos da assinatura são inseridos nos dados assinados. Por exemplo, se você tiver assinado a mensagem a seguir usando uma estrutura de assinatura envelopada:

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

O documento assinado resultado terá esta aparência:

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

Observe que a assinatura contém um único elemento Reference com uma cadeia de caracteres vazia como sua URI. Uma cadeia de caracteres vazia nesse contexto se refere à raiz do documento.

Note também que o algoritmo de transformação especifica que uma transformação de assinatura envelopada foi aplicada. Quando isso ocorre, o XMLSignatureValidator remove automaticamente a assinatura do documento antes da computação da compilação. Isso significa que o cancelador de referência não precisa remover o elemento Signature quando retorna os dados.

O exemplo a seguir ilustra um cancelador de referência para assinaturas envelopadas:

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

Essa classe do cancelador de referências usa uma função construtora com um parâmetro, o signedMessage , para tornar o documento com assinatura envelopada disponível para o método dereference() . Como a referência em uma assinatura envelopada sempre se refere à raiz dos dados assinados, o método dereferencer() grava o documento em uma matriz de bytes e o retorna.

Cancelamento de referência de URIs em assinaturas envelopadas e desanexadas

Quando os dados assinados são localizados no mesmo documento que a própria assinatura, as URIs das referências geralmente usam a sintaxe XPath ou XPointer para lidar com elementos assinados. As recomendações do W3C para processamento e sintaxe de assinatura XML recomendam somente essa sintaxe, portanto você deve basear sua implementação nas assinaturas que espera encontrar (e adicionar verificação de erro suficiente par lidar bem com a sintaxe sem suporte).

A assinatura de um aplicativo do AIR é um exemplo de uma assinatura envelopada. Os arquivos no aplicativo são listados em um elemento Manifest. O elemento Manifest é tratado no atributo Reference da URI usando a sequência de caracteres “#PackageContents”, que se refere à Id do 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>

Um cancelador de referência para validar essa assinatura deve usar a sequência de caracteres da URI contendo "#PackageContents" do elemento Reference e retornar o elemento Manifest em um objeto ByteArray. O símbolo "#" se refere ao valor de um atributo da Id do elemento.

O exemplo a seguir implementa um cancelador de referência para validar assinaturas do aplicativo do AIR. A implementação é mantida simples contando com a estrutura conhecida de uma assinatura do AIR. Um cancelador de referência de finalidade geral pode ser bem mais complexo.

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 você verifica esse tipo de assinatura, somente os dados do elemento Manifest são validados. Os arquivos reais do pacote não são verificados. Para verificar se os arquivos do pacote estão adulterados, é necessário ler os arquivos, computar a compilação SHA256 e comparar o resultado à compilação registrada no manifesto. O XMLSignatureValidator não verifica automaticamente essas referência secundárias.

Nota: Esse exemplo é fornecido somente para ilustrar o processo de validação da assinatura. Há pouco uso em um aplicativo do AIR que valida a própria assinatura. Se o aplicativo já tiver sido adulterado, o agente de adulteração pode simplesmente remover a verificação de validação.

Computação de valores de compilação para recursos externos

O AIR não inclui funções internas para computar compilações SHA256, mas o FlexSDK não inclui uma classe de utilitário SHA256. O SDK inclui também a classe de utilitário do codificador Base64 que é útil para comparar a compilação computada com a compilação armazenada em uma assinatura.

O exemplo de função a seguir lê e valida os arquivos de um manifesto do pacote do 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; 
}

A função realiza um loop por todas as referências do elemento Manifest. Para cada referência, a compilação SHA256 é computada, codificada no formato base64 e comparada com a compilação no manifesto. As URIs no pacote do AIR se referem aos caminhos relativos ao diretório do aplicativo. Os caminhos são resolvidos com base na localização do arquivo de assinatura, que está sempre no subdiretório META-INF dentro do diretório do aplicativo. Observe que a classe Flex SHA256 retorna a compilação como sequência de caracteres hexadecimais. Essa sequência de caracteres deve ser convertida em um ByteArray contendo os bytes representados pela sequência de caracteres hexadecimais.

Para usar as classes mx.utils.SHA256 e Base64Encoder no Flash CS4, você pode localizar e copiar tais classes no diretório de desenvolvimento do seu aplicativo ou compilar uma biblioteca de SWF contendo as classes que usam o SDK do Flex.

Cancelamento de referência de URIs em assinaturas desanexadas que referenciam dados externos

Quando uma URI referencia um recurso externo, os dados devem ser acessados e carregados em um objeto ByteArray. Se a URI contiver uma URL absoluta, será uma simples questão de ler um arquivo ou solicitar uma URL. Se, como ocorre com a maioria dos casos, a URI contiver um caminho relativo, sua implementação IURIDereferencer deverá incluir uma forma de resolver os caminhos para arquivos assinados.

O exemplo a seguir usa um objeto File inicializado quando a instância do cancelador de referência é construída como base para resolver arquivos assinados.

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

A função dereference() simplesmente localiza o arquivo endereçado pela URI de referência, carrega o conteúdo do arquivo em uma matriz de bytes e retorna o objeto ByteArray.

Nota: Antes de validar referências externas remotas, considere a possibilidade de o aplicativo estar vulnerável a um "phone home" ou um tipo de ataque semelhante por um documento de assinatura construído maliciosamente.