19 сентября 2011 г.

Выключение компрессии в OpenSSL 0.9.8

В OpenSSL реализована встроенная поддержка zlib и по умолчанию включена компрессия пересылаемых пакетов данных. Причем при сжатии очередного пакета используется информация об уже сжатых и отосланых пакетах. Например, если вы отсылаете три одинаковых пакета по 400 байт, и первый пакет сжимается до 275 байт, то второй и третий пакеты в сжатом виде займут всего по 12 байт. Такой подход очень экономит трафик, но применим только к протоколу TLS, потому что он гарантирует доставку пакетов и сохраняет последовательность, в которой пакеты были отосланы. Понятно, что для протоколов с негарантированной доставкой такой подход к сжатию не может быть применён. Это подтверждает RFC 3749 "TLS Compression Methods", в котором написано следующее:

Some compression methods have the ability to maintain state/history information when compressing and decompressing packet payloads. The compression history allows a higher compression ratio to be achieved on a stream as compared to per-packet compression, but maintaining a history across packets implies that a packet might contain data needed to completely decompress data contained in a different packet. History maintenance thus requires both a reliable link and sequenced packet delivery.

Протокол DTLS, выбранный для нашего проекта, был разработан специально для UDP и не гарантирует доставку пакетов. Но большей частью он основан на TLS, и по идее реализация протокола DTLS должна учитывать требования RFC 3749.

К сожалению, в OpenSSL этот момент не учитывается и в DTLS сжатие используется точно также, как и в TLS. Совершенно очевидно, что при потере одного из пакетов вся передача будет нарушена, и даже если этот пакет придет позже, то восстановить исходную последовательность принимающая сторона уже не сможет. Такой неприятный момент должен решаться простым отключением компрессии при использовании DTLS, однако в OpenSSL 0.9.8 это превращается в проблему, потому что в API просто нет функции отключающей компрессию. Вот цитата из документации на метод SSL_COMP_add_compression_method:

An OpenSSL server will match the identifiers listed by a client against its own compression methods and will unconditionally activate compression when a matching identifier is found. There is no way to restrict the list of compression methods supported on a per connection basis.

Начиная с OpenSSL 1.0.0 добавлена опция SSL_OP_NO_COMPRESSION, которая делает то, что мне нужно, но в версии 0.9.8 ее нет. В поисках решения я порылся в сети, но ничего внятного, кроме вот этого не нашел:

void disable_openssl_compression()
{
  STACK_OF(SSL_COMP)* comp_methods = SSL_COMP_get_compression_methods();
  sk_SSL_COMP_zero(comp_methods);
}

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

Между тем, изучив исходный код OpenSSL, я обнаружил, что информация о доступных компрессорах хранится в простом списке, в который можно добавлять свои компрессоры с помощью функции SSL_COMP_add_compression_method. А раз можно добавлять, то значит должен быть способ и удалять элементы списка. Такой функции в OpenSSL API нет, но после изучения исходников функции SSL_COMP_add_compression_method можно написать свою, не пользуясь хаками, а только предоставленным API:

void disable_openssl_compression (void)
{
  int n = sk_SSL_COMP_num(SSL_COMP_get_compression_methods());
  for (int j = 0; j < n; ++j)
  {
    SSL_COMP *comp = sk_SSL_COMP_pop(SSL_COMP_get_compression_methods());
    OPENSSL_free(comp);
  }
}

12 сентября 2011 г.

Этот загадочный SecurityError (Flex 3)

После очередной порции изменений моё Flash-приложение, загруженное на сервер, перестало работать. То есть не совсем перестало, потому что по click-событиям выполнялось то, что нужно, но визуальные элементы вообще перестали хоть как-то отображаться — кнопки не хайлайтились, текст не скроллился, картинки не отображались... Сложилось такое ощущение, что перестали обрабатываться сообщения, отвечающие за отображение компонентов.

Из-за того, что часть приложения продолжала функционировать, я даже не сразу заметил проблему. При этом локально всё замечательно продолжало работать. Я довольно долго тупил, думая, что это проблемы совместимости установленных Flash-плееров в браузерах и в системе. Но через какое-то время понял, что такое различие в поведении локального приложения и приложения на сервере может вызвать SecurityError. Только где? (Тут я в очередной раз порадовался своей привычке делать маленькие, логически законченные коммиты. Локализовывать баг при таком подходе одно удовольствие и всего лишь вопрос времени.)

Причина обнаружилась в установленном smoothBitmapContent="true" для одного из тегов mx:Image. Так как картинка для тега загружалась не с сервера приложения, то контент по определению считался небезопасным. Доступ к битовым данным в этом случае вызовет SecurityError, а smoothBitmapContent="true" как раз требует такого доступа. Это изменение попало в репозиторий совершенно случайно, в результате неудавшихся экспериментов, но оказалось фатальным для приложения.

Для меня остается загадкой, почему SecurityError вызвал именно такое поведение, когда одна половина приложения работает, а другая половина — нет.

5 сентября 2011 г.

Выбор реализации DTLS

Как мне удалось выяснить, использование протокола DTLS для защиты UDP трафика требует минимальных трудозатрат при максимальной эффективности. К тому же еще и является платформонезависимым. В общем, встал вопрос о выборе существующей реализации этого протокола для использования в проекте.

К сожалению, удалось найти только три библиотеки, поддерживающие DTLS, и это при том, что протокол TSL, предназначенный для TCP, поддерживают около десятка некоммерческих библиотек, не говоря уже о коммерческих реализациях. Но как бы там ни было, выбирать приходится из того, что есть.

  1. CyaSSL. С момента появления поддержки DTLS в библиотеке CyaSSL прошло уже два с половиной года, но текущая версия (2.0.0 RC2) поддерживает DTLS всё ещё в экспериментальном режиме, а значит CyaSSL не может быть рассмотрена как постоянное решение для более-менее серьёзных продуктов.
  2. OpenSSL. Несмотря на то, что стандарт DTLS был опубликован в 2006 году, поддержка этого протокола в OpenSSL появилась ещё в версии 0.9.8 (2005 год). Версия 0.9.8 входит во многие дистрибутивы Linux, много раз фиксилась, и по всей видимости является стабильной. Хотя и имеет довольно уродский API.
  3. GnuTLS. Поддерживает DTLS начиная с версии 3.0.0, вышедшей в марте 2011 года. К сожалению, я не нашел дистрибутива Linux, в который была бы включена эта версия GnuTLS. Более того, похоже, что прежде, чем она будет куда-то включена, пройдет немало времени. Например, в последнюю версию Ubuntu (11.04) включена GnuTLS 2.8.6, а в будущей Ubuntu 11.10, будет использоваться GnuTLS 2.10.5. Когда будет поддержка 3.0.0 — неизвестно. Многие скажут, мол, собрать библиотеку из исходников не представляет никаких проблем, но на деле GnuTLS тянет за собой ряд библиотек, для которых также нет нативной поддержки в существующих дистрибутивах Linux, а значит они тоже должны быть собраны самостоятельно со всеми вытекающими. Дело вкуса.

Что касается переносимости, то OpenSSL и CyaSSL поддерживают Win/Linux/Mac, GnuTLS поддерживет Windows и "most Unix platforms", поэтому проблем с переходом на другую платформу быть не должно.

Как это не печально, выбор очевиден (по крайней мере для меня) — OpenSSL является самой стабильной и широко распространенной реализацией DTLS, с использованием этой реализации не должно возникнуть серьёзных проблем. Хотя лично я предпочел бы интерфейс GnuTLS, он какой-то более уютный что ли...