Поддержка протоколов TLS/SSL для сокетного соединения на AS3

в 10:43, , рубрики: Action Script, actionscript 3.0, as3, Flash-платформа, socket, метки: , ,

Поддержка протоколов TLS/SSL для сокетного соединения на AS3

Мы разрабатываем Flash клиент для клиент-серверного приложения с постоянным сокетным соединением и нам важно защититься от прослушки трафика. Один из способов такой защиты — SSL/TLS шифрование. Во Flash сделать это можно двумя путями — использовать родной SecureSocket или TLSSocket из библиотеки as3crypto. Ниже мы обсудим плюсы и минусы, производительность, а также проблемы с которыми мы столкнулись при внедрении обоих вариантов.

Сводная таблица сравнения

SSL/TLS это надстройка над сокетом для предотвращения прослушивания и выполняет две функции — аутентификация участников (сервер и клиент) и шифрование трафика. Основной функционал сокетов для наглядности оформлен в виде таблицы:
Поддержка протоколов TLS/SSL для сокетного соединения на AS3
Сравнение SecureSocket и TLSSocket, “+” — не выявелено проблем, “-” — не поддерживается или не работает, “±” — работает но с ошибками или работает не для всех случаев, “?” — не удалось проверить из-за предыдущих ошибок, “-?” — заявлено что работает, но подтвердить не удалось

Подробнее по пунктам

SecureSocket

Минимальная версия Flash Player

Для работы SecureSocket требуется Flash Player 11-й версии и выше, который установлен только у 75% пользователей, т.е. для оставшихся 25% в любом случае придется использовать TLSSocket.

Работа с сертификатами

SecureSocket интегрирован в систему, что позволяет ему работать с системным хранилищем сертификатов и системными средствами проверки их подлинности, т.е. серверный сертификата реально будет проверен через центр сертификации, чего никогда не сможет сделать TLSSocket. SecureSocket так же позволяет добавить собственный сертификат, который будет считаться доверенным, но все равно проверит его на другие ошибки (срок годности, несоответствие Common Name в сертификате и имени сервера) и если что не так, обрубит соединение, поэтому старые тестовые сертификаты (думаю, почти у каждого разработчика такие найдутся) скорее всего не подойдут.

SSL

SSL 3.0 (далее по тексту под SSL всегда подразумевается SSL 3.0) тестировали на своих серверах в принудительном SSL режиме. Клиент закрывал соединение с неопознанным статусом проверки сертификата (serverCertificateStatus = invalid) на всех сертификатах, что мы пробовали, в том числе официально подписанном. Замечу, что на 443й порт публичных серверов (google.com, yandex.ru) сокет заходит успешно, но там скорее всего выбирается TLS протокол, а форсировать использование только одного из двух протоколов TLS или SSL SecureSocket не позволяет, так же как и не обращает внимание на настройки TLS/SSL в браузере, в котором запускается swf. Не берусь утверждать, что SSL 3.0 в принципе не работает, все-таки его поддержка заявлена в официальной документации, но нам заставить его работать не удалось.

TLS

TLS заработал сразу и на обоих сертификатах. С работающим сокетом удалось оценить скорость работы по сравнению с незащищенным протоколом, здесь нас ждало большое разочарование, об этом ниже.

TLSSocket

Работа с сертификатами

В работе с сертификатами TLSSocket имеет несколько слабых мест:

  • Имеются различные не поправленные баги при парсинге некоторых типов сертификатов. У нас не распарсился сертификат центра сертификации и предложенный в issue 26 патч не помог
  • Нельзя добавить собственный сертификат, который будет автоматически считаться доверенным. В паре с предыдущим пунктом мы в принципе лишились возможности проверить серверный сертификат. Функционал придется дописывать самостоятельно (пример реализации ниже), либо работать в режиме trustAllCertificates оставив дыру в безопасности.
  • Нет возможности работать с сертификатами, установленными на системе клиента. Судя по наличию свойства TLSConfig::localKeyStore задумка была, но, видимо, невозможность программными силами AS3 считать такой список притормозила реализацию.

Однако есть и положительный момент, библиотека предоставляет несколько флагов для работы даже с невалидными сертификатами (несовпадение имени сервера в сертификате и реального, самоподписанные сертификаты) открытость кода позволяет дописать недостоющий функционал (например флаг игнорирования истечения срока годности сертификата или то же добавление собственных сертификатов)

Патч для добавления собственных доверенных сертификатов

Небольшой патч, добавляющий поддержку внешних сертификатов как доверенных в DER формате. Для реализации просто берём MD5 хеш всего сертификата и сравниваем с тем, что пришло с сервера, если совпадает — сертификат доверенный.

  1. В класс com.hurlant.crypto.tls.TLSConfig добавить методы добавления и проверки своих сертификатов
    		/**
    		 * Add own your own trusted certificate
    		 * @param cert A ByteArray object containing a DER-encoded X.509 digital certificate.
    		 */
    		public function addTrustedCertificate(cert:ByteArray):void
    		{
    			var md5:String = Hex.fromArray(new MD5().hash(cert));
    			trustedCertificates[md5] = true;
    		}
    		
    		/**
    		 * Check whether the certificate is marked as trusted
    		 * @param cert X509Certificate object to check
    		 */
    		public function isTrustedCertificate(cert:X509Certificate):Boolean
    		{
    			var md5:String = cert.md5;
    			return trustedCertificates[md5];
    		}
    
  2. В класс com.hurlant.crypto.cert.X509Certificate добавить геттер на md5 хеш и свойство _bytes (сертификат в DER кодировке).
    		private function load():void
    		{
    			...		
    			} else if (p is ByteArray) {
    				b = p;
    			}
    			_bytes = b;
    			...
    		}
    
    		private var _bytes:ByteArray;
    		private var _md5:String;
    		/**
    		 * get md5 hash for certificate
    		 */
    		public function get md5():String
    		{
    			if(_md5) 
    			{
    				return _md5;
    			}
    			
    			if (_bytes) 
    			{
    				_md5 = Hex.fromArray(new MD5().hash(_bytes));
    			} else 
    			{
    				throw new Error("get md5: Invalid x509 Certificate parameter");
    			}
    			
    			return _md5;
    		}
    
  3. в классе com.hurlant.crypto.tls.TLSEngine дописать проверку доверенности серверного сертификата в метод loadCertificates строка 810:
    			...
    			} else if (_config.isTrustedCertificate(firstCert)) {
    				certTrusted = true;
    			} else if(_config.trustSelfSignedCertificates )
    			{
    				// Self-signed certs
    				certTrusted = firstCert.isSelfSigned(new Date); 
    			}else
    			{
    			...
    

Пример использования:

package
{
import com.hurlant.crypto.tls.TLSConfig;
import com.hurlant.crypto.tls.TLSEngine;
import com.hurlant.crypto.tls.TLSSecurityParameters;
import com.hurlant.crypto.tls.TLSSocket;
import com.hurlant.util.der.PEM;

import flash.display.Sprite;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;
import flash.utils.ByteArray;

public class addcerttest extends Sprite
{
	[Embed(source="cert.pem", mimeType="application/octet-stream")]
	private static const cert_pem:Class;
	
	public function addcerttest()
	{
		var cert_pem_bytes:ByteArray = new cert_pem();
		var cert_der:ByteArray = PEM.readCertIntoArray(cert_pem_bytes.readUTFBytes(cert_pem_bytes.bytesAvailable));
		
		var config:TLSConfig = new TLSConfig(TLSEngine.CLIENT, null, null, null, null, null, TLSSecurityParameters.PROTOCOL_VERSION);
		config.addTrustedCertificate(cert_der);
		
		var socket:TLSSocket = new TLSSocket(null, 0, config);
		socket.addEventListener(Event.CONNECT, log);
		socket.addEventListener(Event.CLOSE, log);
		socket.addEventListener(IOErrorEvent.IO_ERROR, log);
		socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, log);
		
		socket.connect("my.app.server", 443);
	}
	
	private function log(event:Event):void
	{
		trace(event.type);
	}
}
}

SSL/TLS

У TLSSocket шифрование трафика заработало в обоих протоколах, но в SSL ломается шифрование пакетов на клиенте после нескольких минут работы клиента. Ломается скорее всего из-за какой-то баги в библиотеке, искать не стали, т.к. TLS работает без проблем.
В as3crypto вообще довольно большой список багов и проблема усугубляется тем, что разработчики перестали обновлять библиотеку.

Производительность

SecureSocket

С SecureSocket возникла новая проблема — периодически (раз в 5-10 запросов) сокет «уходит в себя» на 2-8 секунд без видимых причин. Я не берусь утверждать, что такие лаги будут у всех, но у нас они были, причем на том же сервере. на котором TLSSocket работал быстро, т.е. дело на клиентской стороне. Тестировали в “чистой” демке без обвязки в виде приложения. Зависимость времени парсинга от размера пакета установить не удалось, пробовали пакеты от 30 байт до 5 кб, зависания появлялись в разные времена на случайных пакетах, процессор во время зависаний не грузился, fps не падал. Сам же парсинг пакетов быстрый, если брать только пакеты без зависаний разницы между Socket и SecureSocket незаметно, в то время как TLSSocket все же медленнее чем Socket. Попробую разобраться с этим позже, т.к. хотелось бы все же иметь быстро работающий родной вариант.

TLSSocket

Поскольку вся реализация здесь на as3 время парсинга пакетов линейно пропорционально размеру и для пакетов на 2-7 кб (максимальные в нашем проекте) на core i7 составило 40-120 мс соответственно, мелкие пакеты с runtime оповещениями парсятся влёт (5-20 мс для пакетов в 100-500 байт) и никаких лагов не вызывают. Для пользователя добавление SSL/TLS будет заметно только на больших пакетах.

Выкладка swf

Все проверили и отладили, теперь, выкладываем сборку на http сервер, не забыв настроить socket policy server. Запускаем приложение, видим, что оба сокета не работают. Причины у каждого свои: TLSSocket не работает из-за issue 4 — клиент посылает запрос на «рукопожатие» раньше, чем устанавливается соединение. С SecureSocket немного сложнее, чтобы Flash Player разрешил открыть TLS/SSL соединение, файл политик с сервера придется отдавать так же по TLS/SSL протоколу, т.е. потребуется доработка раздатчика этого файла.

Выводы

SecureSocket интегрирован в систему, что позволяет ему работать с системным хранилищем сертификатов и системными средствами проверки их подлинности, чем не может похвастаться TLSSocket. Однако на этом преимущества SecureSocket заканчиваются. Из явных недостатков отмечу секундные лаги при парсинге входящих данных, неработающий SSL 3.0 и вообще отладочную молчаливость если что-то не работает, свойственную многим родным as3 решениям. TLSSocket же отличается быстрой скоростью и возможностью поправить (или по крайней мере разобраться в причине) ошибки в работе, предоставляя свои исходники.

Спасибо, кто дочитал до конца, надеюсь материал окажется полезным и кому-то сэкономит время. Опечатки и ошибки прошу отправлять в личку. Неточности и дополнения буду рад обсудить в комментариях.
Ссылка на GitHub as3crypto_patched с патчами, которые упоминались в статье и функционалом добавления внешних доверенных сертификатов.

Автор: FSB

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js