Реализация интерфейса IURIDereferencer

Adobe AIR 1.5 и более поздних версий

Для проверки XML-подписи необходимо реализовать интерфейс IURIDereferencer. Этот интерфейс отвечает за разрешение идентификаторов URI в элементах Reference документа XML-подписи и возвращает данные, по которым можно рассчитать дайджест. Расчетный дайджест сравнивается с дайджестом в подписи для определения того, не изменялись ли соответствующие данные с момента создания подписи.

Примечание. HTML-приложения AIR должны импортировать SWF-библиотеку, содержащую реализацию ActionScript, чтобы можно было проверить XML-подписи. Интерфейс IURIDereferencer не может быть реализован в JavaScript.

Интерфейс IURIDerefencer содержит один единственный метод, dereference(uri:String) , который должен быть реализован. Объект XMLSignatureValidator вызывает этот метод для каждой ссылки в подписи. Этот метод должен возвращать данные в виде объекта ByteArray.

В большинстве случаев также потребуется добавить свойства или методы, которые позволяют объекту dereferencer находить упомянутые данные. Например, если подписанные данные находятся в том же документе, что и подпись, можно добавить переменную member, обеспечивающую ссылку на документ XML. Метод dereference() затем может использовать эту переменную вместе с идентификатором URI для обнаружения упомянутых данных. Сходным образом, если подписанные данные размещаются в каталоге локальной файловой системы, методу dereference() может потребоваться свойство, предоставляющее путь к этому каталогу, для того чтобы найти упомянутые файлы.

Класс XMLSignatureValidator полностью полагается на объект dereferencer при интерпретации строк с идентификаторами URI. Стандартные правила разыменования идентификаторов URI приведены в разделе 4.3.3 рекомендаций W3C для синтаксиса и порядка обработки XML-подписей.

Разыменование идентификаторов URI во вложенных подписях

При создании вложенной XML-подписи ее элементы вставляются в подписанные данные. Например, если следующее сообщение подписывается с помощью структуры вложенной подписи.

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

Получившийся в результате подписанный документ будет выглядеть следующим образом.

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

Обратите внимание, что подпись содержит один элемент Reference с пустой строкой в качестве его идентификатора URI. Пустая строка в данном контексте указывает на корневой каталог документа.

Также обратите внимание, что алгоритм преобразования указывает на то, что было применено преобразование вложенной подписи. После преобразования вложенной подписи класс XMLSignatureValidator автоматически удаляет ее из документа перед расчетом дайджеста. Это означает, что объект dereferencer не должен удалять элемент Signature при возвращении данных.

В следующем примере проиллюстрирована работа объекта dereferencer для вложенных подписей:

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

Класс dereferencer использует функцию конструктора с параметром signedMessage для создания документа со вложенной подписью, доступного для метода dereference() . Поскольку ссылка во вложенной подписи всегда указывает на корневой каталог подписанных данных, метод dereferencer() записывает документ в Byte Array и возвращает его.

Разыменовывание идентификаторов URI в обертывающих и отдельных подписях

Если подписанные данные расположены в том же документе, что и сама подпись, то идентификаторы URI в ссылках обычно используют синтаксис XPath или XPointer для адресации подписанных элементов. В рекомендациях W3C для синтаксиса и порядка обработки XML-подписей даются только общие рекомендации синтаксиса, поэтому на практике необходимо реализовывать обработку тех подписей, которые предположительно будут встречаться (и добавлять достаточные механизмы проверки ошибок для обработки неподдерживаемого синтаксиса).

Подпись в приложении AIR является примером обертывающей подписи. Файлы приложения перечисляются в элементе Manifest. Элемент Manifest адресуется атрибутом Reference URI, используя строку "#PackageContents", которая ссылается на идентификатор элемента 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>

Объект dereferencer для проверки этой подписи должен брать строку URI, содержащую "#PackageContents" из элемента Reference, а возвращать элемент Manifest в объекте ByteArray. Символ # указывает на значение атрибута идентификатора элемента.

В следующем примере реализуется объект dereferencer для проверки подписей приложения AIR. Эта реализация достаточно проста, т.к. основана на известной структуре подписи в AIR. Многоцелевой объект dereferencer может быть значительно более сложным.

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

Когда проверяется такой тип подписей, фактически проверяются только данные в элементе Manifest. Фактические же файлы в пакете не проверяются совсем. Чтобы проверить файлы пакета на предмет фальсификации, необходимо считать эти файлы, рассчитать дайджест SHA256 и сравнить результат с дайджестом, записанным в манифесте. Класс XMLSignatureValidator не выполняет автоматической проверки таких вторичных ссылок.

Примечание. Этот пример представлен только, чтобы проиллюстрировать процесс проверки подписи. Нет никакого смысла в том, чтобы приложение AIR проверяло свою собственную подпись. Ведь если приложение уже подверглось вмешательству, фальсификатор запросто мог удалить такую проверку.

Вычисления значений дайджестов для внешних ресурсов

Adobe AIR не содержит встроенных функций для расчета дайджестов SHA256, но в пакет Flex SDK включен служебный класс SHA256. Этот пакет также содержит служебный класс с кодировщиком Base64, который помогает при сравнении расчетного дайджеста с дайджестом, хранимым в подписи.

В следующем примере функция считывает и проверяет файлы, указанные в манифесте пакета 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; 
}

Функция циклически обрабатывает все ссылки в элементе Manifest. Для каждой ссылки рассчитывается дайджест SHA256, который кодируется в формат base64, а затем сравнивается с дайджестом в манифесте. Идентификаторы URI в пакете AIR указывают пути относительно каталога приложения. Эти пути разрешаются с учетом местоположения файла подписи, который всегда находится в подкаталоге META-INF в каталоге приложения. Обратите внимание, что класс Flex SHA256 возвращает дайджест в виде строки шестнадцатеричных символов. Это строка должна преобразовываться в массив ByteArray, содержащий байты, передающие эту шестнадцатеричную строку.

Чтобы использовать классы mx.utils.SHA256 и Base64Encoder в Flash CS4, можно либо найти и скопировать эти классы в каталог разработки приложения, либо скомпилировать библиотеку SWF, содержащую классы с помощью Flex SDK.

Разыменование идентификаторов URI в отдельных подписях, ссылающихся на внешние данные

Если идентификатор URI ссылается на внешний источник, необходимо выполнить доступ к данным и загрузить их в объект ByteArray. Если идентификатор URI содержит абсолютный адрес URL, то все сводится к простому считыванию файла или запросу URL. Если, как это, вероятно, бывает в большинстве случаев, идентификатор URI содержит относительный путь, то реализация интерфейса IURIDereferencer должна включать способ разрешения путей для подписанных файлов.

В следующем примере используется объект File, инициализируемый при создании экземпляра класса dereferencer в качестве основы для разрешения подписанных файлов.

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

Функция dereference() просто находит файл, адресуемый идентификатором URI ссылки, загружает содержимое этого файла в байтовый массив и возвращает объект ByteArray.

Примечание. Прежде чем выполнять проверку удаленных внешних ссылок, оцените уязвимость своего приложения для «звонков домой» или подобного типа атак со стороны злоумышленно созданного документа подписи.