Подводные камни HTTPS в Android

Проснувшись прекрасным праздничным посленовогодним утром (в два часа дня) я привычным движением подобрал планшет и тыкнул пальцем в мою разработку чтоб посмотреть на картинку Солнца.

Не сказать что я был удивлен, когда вместо диска обнаружилась пустота, все таки данные берутся с https://sdo.gsfc.nasa.gov/ и уже бывало что он был не доступен целиком или в части фотографий. Однако увы в браузере все грузилось и было на месте, более того даже на своем месте, т.е. это не смена url.

Таким образом меня ждал очень интересный день, ибо что может быть лучше чем ловить внезапный баг в уже более месяца опубликованном приложении :-)

Зацепки

Первое что бросилось в глаза — сайт перешел с http на https.

Второй сюрприз, в версиях android 4.4.2 и выше все работало как надо. Т.е. мне еще повезло что планшет у меня работает на 4.2.

Ну и третье, единственная ошибка в логах: ”Server closed connection”

Рытье

После этого конечно возникло ощущение что какая то беда в протоколах, был нарыт сайт:

который собственно помог, его основная функция тестирование ssl сайтов, применительно к нашему подопечному https://www.ssllabs.com/ssltest/analyze.html?d=sdo.gsfc.nasa.gov выяснилось что он поддерживает только два протокола: TLS 1.1 и TLS 1.2, что в общем то правильно так как SSL2–3 признаны устаревшими.

И что самое приятное в результатах теста есть часть: “Hadshake Simulation” где сразу видно что сайт нормально работает только с Android 4.4.2 и выше, а так же видна причина, древние версии андроида не работают с протоколами TLS1.1 и 1.2. Затем выяснилось что начиная с android API16 (4.1) данные протоколы встроены, но отключены по умолчанию. И тут мне повезло так как минимальная поддерживаемая версия android моего приложения как раз 4.1.

Решение

Включаем TLS1.1 и TLS1.2

Моя программа стильна модна молодежна :-D Т.е. написана на kotlin и использует всякие Dagger2, rxJava, Realm, Retrofit2 и т.п. (при этом кстати весит меньше 8Мб)

В данном контексте важно что для поиска и загрузки изображений используется Retrofit2, который в свою очередь использует okHttpClient в котором есть методы для установки своей sslSocketFactory.

Путем гугления https://github.com/square/okhttp/issues/2372 было найдено готовое решение которое на kotlin выглядит следующим образом:

При использовании ProGuard данный класс пришлось добавить в исключения, иначе хоть сборка и проходила без ошибок в процессе исполнения один класс не находил другой

Так как в котлине возможны экстеншены к классам то можно создать такую симпатичную функцию к OkHttpClient.Bilder’у:

Соответственно в коде включение tls выглядит как будто эта функция имеется в самом классе, примерно так:

OkHttpClient.Builder().enableTls12OnPreLollipop().build()

В этом один из плюсов kotlin, но это уже другая тема.

После этого все заработало как надо.

PS: Небольшая правда уже не сильно полезная ссылка, подробно показывающая какие протоколы в каких версиях андроида используются