Implementing the IURIDereferencer interface

Adobe AIR 1.5 and later

To validate an XML signature, you must provide an implementation of the IURIDereferencer interface. The implementation is responsible for resolving the URIs within the Reference elements of an XML signature document and returning the data so that the digest can be computed. The computed digest is compared with the digest in the signature to determine if the referenced data has been altered since the signature was created.

Note: HTML-based AIR applications must import a SWF library containing an ActionScript implementation in order to validate XML signatures. The IURIDereferencer interface cannot be implemented in JavaScript.

The IURIDerefencer interface has a single method, dereference(uri:String) , that must be implemented. The XMLSignatureValidator object calls this method for each reference in the signature. The method must return the data in a ByteArray object.

In most cases, you will also need to add properties or methods that allow your dereferencer object to locate the referenced data. For example, if the signed data is located in the same document as the signature, you could add a member variable that provides a reference to the XML document. The dereference() method can then use this variable, along with the URI, to locate the referenced data. Likewise, if the signed data is located in a directory of the local file system, the dereference() method might need a property providing the path to that directory in order to resolve the referenced files.

The XMLSignatureValidator relies entirely on the dereferencer for interpreting URI strings. The standard rules for dereferencing URIs are given in the section 4.3.3 of the W3C Recommendation for XML Signature Syntax and Processing.

Dereferencing URIs in enveloped signatures

When an enveloped XML signature is generated, the signature elements are inserted into the signed data. For example, if you signed the following message using an enveloped signature structure:

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

The resulting signed document will look like this:

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

Notice that the signature contains a single Reference element with an empty string as its URI. An empty string in this context refers to the root of the document.

Also notice that the transform algorithm specifies that an enveloped signature transform has been applied. When an enveloped signature transform has been applied, the XMLSignatureValidator automatically removes the signature from the document before computing the digest. This means that the dereferencer does not need to remove the Signature element when returning the data.

The following example illustrates a dereferencer for enveloped signatures:

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

This dereferencer class uses a constructor function with a parameter, signedMessage , to make the enveloped signature document available to the dereference() method. Since the reference in an enveloped signature always refers to the root of the signed data, the dereferencer() method writes the document into a byte array and returns it.

Dereferencing URIs in enveloping and detached signatures

When the signed data is located in the same document as the signature itself, the URIs in the references typically use XPath or XPointer syntax to address the elements that are signed. The W3C Recommendation for XML Signature Syntax and Processing only recommends this syntax, so you should base your implementation on the signatures you expect to encounter (and add sufficient error checking to gracefully handle unsupported syntax).

The signature of an AIR application is an example of an enveloping signature. The files in the application are listed in a Manifest element. The Manifest element is addressed in the Reference URI attribute using the string, “#PackageContents”, which refers to the Id of the Manifest element:

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

A dereferencer for validating this signature must take the URI string containing, "#PackageContents" from the Reference element, and return the Manifest element in a ByteArray object. The “#” symbol refers to the value of an element Id attribute.

The following example implements a dereferencer for validating AIR application signatures. The implementation is kept simple by relying on the known structure of an AIR signature. A general-purpose dereferencer could be significantly more complex.

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

When you verify this type of signature, only the data in the Manifest element is validated. The actual files in the package are not checked at all. To check the package files for tampering, you must read the files, compute the SHA256 digest and compare the result to digest recorded in the manifest. The XMLSignatureValidator does not automatically check such secondary references.

Note: This example is provided only to illustrate the signature validation process. There is little use in an AIR application validating its own signature. If the application has already been tampered with, the tampering agent could simply remove the validation check.

Computing digest values for external resources

AIR does not include built-in functions for computing SHA256 digests, but the Flex SDK does include a SHA256 utility class. The SDK also includes the Base64 encoder utility class that is helpful for comparing the computed digest to the digest stored in a signature.

The following example function reads and validates the files in an AIR package manifest:

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

The function loops through all the references in the Manifest element. For each reference, the SHA256 digest is computed, encoded in base64 format, and compared to the digest in the manifest. The URIs in an AIR package refer to paths relative to the application directory. The paths are resolved based on the location of the signature file, which is always in the META-INF subdirectory within the application directory. Note that the Flex SHA256 class returns the digest as a string of hexadecimal characters. This string must be converted into a ByteArray containing the bytes represented by the hexadecimal string.

To use the mx.utils.SHA256 and Base64Encoder classes in Flash CS4, you can either locate and copy these classes into your application development directory or compile a library SWF containing the classes using the Flex SDK.

To use the mx.utils.SHA256 and Base64Encoder classes in an HTML-based AIR application, you can compile a library SWF containing the classes using the Flex SDK and add the SWF to your HTML page using a <script> tag.

Dereferencing URIs in detached signatures referencing external data

When a URI refers to an external resource, the data must be accessed and loaded into a ByteArray object. If the URI contains an absolute URL, then it is simply a matter of reading a file or requesting a URL. If, as is probably the more common case, the URI contains to a relative path, then your IURIDereferencer implementation must include a way to resolve the paths to the signed files.

The following example uses a File object initialized when the dereferencer instance is constructed as the base for resolving signed files.

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

The dereference() function simply locates the file addressed by the reference URI, loads the file contents into a byte array, and returns the ByteArray object.

Note: Before validating remote external references, consider whether your application could be vulnerable to a “phone home” or similar type of attack by a maliciously constructed signature document.

// Ethnio survey code removed