Безопасность с сетевыми протоколами

Для защиты данных приложения при взаимодействии клиента и сервера используется протокол TLS.

В этой статье приведены рекомендации по использованию безопасных сетевых протоколов и инфраструктуры открытых ключей (PKI). Подробную информацию можно найти в обзоре безопасности Android и обзоре разрешений.

Основные понятия

У сервера с сертификатом TLS есть открытый ключ и соответствующий ему закрытый ключ. Сервер использует криптографию с открытым ключом, чтобы подписать свой сертификат во время подтверждения TLS.

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

Однако серверы могут использовать ротацию ключей, чтобы заменить открытый ключ сертификата новым. Изменение конфигурации сервера требует обновления клиентского приложения. Если сервер является сторонним веб-сервисом, например браузером или почтовым приложением, определить, когда нужно обновить клиентское приложение, сложнее.

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

Список доверенных ЦС обычно можно найти на платформе хоста. В Android 8.0 (уровень API 26) входит более 100 центров сертификации, которые обновляются в каждой версии и не меняются в зависимости от устройства.

Клиентским приложениям нужен механизм проверки сервера, поскольку ЦС предлагает сертификаты для множества серверов. Сертификат ЦС идентифицирует сервер по определенному имени, например gmail.com, или с помощью подстановочного знака, например *.google.com.

Чтобы посмотреть информацию о сертификате сервера сайта, используйте команду s_client инструмента openssl, указав номер порта. По умолчанию HTTPS использует порт 443.

Команда передает выходные данные openssl s_client в openssl x509, который форматирует информацию о сертификате в соответствии со стандартом X.509. Команда запрашивает тему (имя сервера) и издателя (ЦС).

openssl s_client -connect WEBSITE-URL:443 | \
  openssl x509 -noout -subject -issuer

Пример HTTPS

Если у вас есть веб-сервер с сертификатом, выданным известным центром сертификации, вы можете сделать защищенный запрос, как показано в следующем коде:

Kotlin

val url = URL("https://wikipedia.org")
val urlConnection: URLConnection = url.openConnection()
val inputStream: InputStream = urlConnection.getInputStream()
copyInputStreamToOutputStream(inputStream, System.out)

Java

URL url = new URL("https://wikipedia.org");
URLConnection urlConnection = url.openConnection();
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);

Чтобы настроить HTTP-запросы, приведите тип к HttpURLConnection. В документации по Android HttpURLConnection есть примеры обработки заголовков запросов и ответов, публикации контента, управления файлами cookie, использования прокси-серверов, кеширования ответов и т. д. Фреймворк Android проверяет сертификаты и имена хостов с помощью этих API.

По возможности используйте эти API. Ниже описаны распространенные проблемы и способы их решения.

Распространенные проблемы с проверкой сертификатов сервера

Предположим, что вместо возврата контента getInputStream() выдает исключение:

javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
        at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:374)
        at libcore.net.http.HttpConnection.setupSecureSocket(HttpConnection.java:209)
        at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:478)
        at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:433)
        at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290)
        at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240)
        at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282)
        at libcore.net.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:177)
        at libcore.net.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:271)

Это может происходить по нескольким причинам:

  1. Центр сертификации, выдавший сертификат сервера, неизвестен.
  2. Сертификат сервера не подписан ЦС, а является самозаверяющим.
  3. В конфигурации сервера отсутствует промежуточный ЦС.

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

Неизвестный центр сертификации

SSLHandshakeException возникает, потому что система не доверяет центру сертификации. Это может быть связано с тем, что у вас есть сертификат от нового центра сертификации, которому Android не доверяет, или потому, что ваше приложение работает на более ранней версии без центра сертификации. Поскольку это закрытый центр сертификации, он редко бывает известен. Чаще всего ЦС неизвестен, потому что он не является общедоступным, а выпущен организацией, например правительством, корпорацией или учебным заведением, для собственного использования.

Чтобы доверять пользовательским ЦС без изменения кода приложения, измените конфигурацию сетевой безопасности.

Внимание! На многих сайтах предлагается альтернативное решение – установить TrustManager, который ничего не делает. В этом случае пользователи, подключенные к общедоступной сети Wi-Fi, могут стать жертвами атаки. Злоумышленник может использовать DNS-уловки, чтобы направить трафик пользователей через прокси-сервер, который выдает себя за ваш сервер. Затем злоумышленник может записывать пароли и другие личные данные. Это возможно, потому что злоумышленник может сгенерировать сертификат, и без TrustManager, подтверждающего, что сертификат получен из надежного источника, вы не сможете заблокировать такую атаку. Поэтому не делайте этого, даже временно. Вместо этого настройте приложение так, чтобы оно доверяло издателю сертификата сервера.

Самозаверенный сертификат сервера

Во-вторых, ошибка SSLHandshakeException может возникать из-за самозаверяющего сертификата, когда сервер выступает в качестве собственного ЦС. Это похоже на неизвестный центр сертификации, поэтому измените конфигурацию сетевой безопасности приложения, чтобы доверять самозаверяющим сертификатам.

Отсутствие промежуточного центра сертификации

В-третьих, ошибка SSLHandshakeException возникает из-за отсутствия промежуточного центра сертификации. Общедоступные ЦС редко подписывают сертификаты серверов. Вместо этого корневой ЦС подписывает промежуточные ЦС.

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

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

Например, вот цепочка сертификатов mail.google.com, полученная с помощью команды openssl: s_client

$ openssl s_client -connect mail.google.com:443
---
Certificate chain
 0 s:/C=US/ST=California/L=Mountain View/O=Google LLC/CN=mail.google.com
   i:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
 1 s:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
   i:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority
---

Это означает, что сервер отправляет сертификат для mail.google.com, выпущенный промежуточным центром сертификации Thawte SGC, и второй сертификат для Thawte SGC, выпущенный основным центром сертификации Verisign, который является доверенным для Android.

Однако сервер может быть не настроен на включение необходимого промежуточного ЦС. Например, сервер, который может вызвать ошибку в браузерах Android и исключения в приложениях Android:

$ openssl s_client -connect egov.uscis.gov:443
---
Certificate chain
 0 s:/C=US/ST=District Of Columbia/L=Washington/O=U.S. Department of Homeland Security/OU=United States Citizenship and Immigration Services/OU=Terms of use at www.verisign.com/rpa (c)05/CN=egov.uscis.gov
   i:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)10/CN=VeriSign Class 3 International Server CA - G3
---

В отличие от неизвестного ЦС или самозаверяющего сертификата сервера, большинство браузеров для компьютеров не выдают ошибку при взаимодействии с этим сервером. Браузеры для компьютеров кешируют доверенные промежуточные ЦС. После того как браузер получит информацию о промежуточном ЦС от одного сайта, ему больше не потребуется этот ЦС в цепочке сертификатов.

Некоторые сайты намеренно делают это для дополнительных веб-серверов, обслуживающих ресурсы. Чтобы сэкономить пропускную способность, они могут размещать основную HTML-страницу на сервере с полной цепочкой сертификатов, а изображения, CSS и JavaScript – без центра сертификации. К сожалению, иногда эти серверы могут предоставлять веб-сервис, к которому вы пытаетесь подключиться из приложения Android, и он может быть не столь терпим.

Чтобы устранить эту проблему, настройте сервер так, чтобы он включал промежуточный ЦС в цепочку сертификатов. Большинство центров сертификации предоставляют инструкции по установке сертификатов на распространенные веб-серверы.

Предупреждения о прямом использовании SSLSocket

До сих пор в примерах использовался протокол HTTPS с HttpsURLConnection. Иногда приложениям требуется использовать TLS отдельно от HTTPS. Например, почтовое приложение может использовать варианты TLS для SMTP, POP3 или IMAP. В таких случаях приложение может использовать SSLSocket напрямую, как это делает HttpsURLConnection.

Описанные выше методы решения проблем с проверкой сертификатов также применимы к SSLSocket. При использовании пользовательского TrustManager в HttpsURLConnection передается SSLSocketFactory. Если вам нужно использовать собственный объект TrustManager с объектом SSLSocket, выполните те же действия и используйте этот объект SSLSocketFactory для создания объекта SSLSocket.

Внимание! SSLSocket не выполняет проверку имени хоста. Приложение должно самостоятельно проверять имя хоста, желательно путем вызова getDefaultHostnameVerifier() с ожидаемым именем хоста. Также имейте в виду, что HostnameVerifier.verify() не выдает исключение при ошибке. Вместо этого он возвращает логическое значение, которое необходимо явно проверить.

Проверка сертификатов.

TLS полагается на центры сертификации, которые выдают сертификаты только подтвержденным владельцам серверов и доменов. В редких случаях ЦС могут быть обмануты или, как в случае с Comodo или DigiNotar, взломаны, в результате чего сертификаты для имени хоста выдаются не владельцу сервера или домена, а кому-то другому.

Чтобы снизить этот риск, Android обрабатывает отзыв сертификатов на уровне системы, используя черный список и проверку сертификатов, не полагаясь на онлайн-проверку. Кроме того, Android будет проверять ответы OCSP, прикрепленные к подтверждению TLS.

Чтобы включить проверку сертификатов в приложении, ознакомьтесь с разделом Включение проверки сертификатов в документации по конфигурации сетевой безопасности.

Как ограничить доступ приложения к определенным сертификатам

Внимание! Мы не рекомендуем использовать в приложениях Android закрепление сертификатов, то есть ограничение списка действительных сертификатов только теми, которые вы ранее авторизовали. Если в будущем вы измените конфигурацию сервера, например выберете другой центр сертификации, приложения с закрепленными сертификатами не смогут подключаться к серверу, пока не будет обновлено клиентское ПО.

Если вы хотите, чтобы ваше приложение принимало только указанные вами сертификаты, обязательно добавьте несколько резервных PIN-кодов, в том числе хотя бы один ключ, который полностью находится под вашим контролем, и достаточно короткий срок действия, чтобы предотвратить проблемы с совместимостью. Конфигурация сетевой безопасности позволяет использовать закрепление сертификатов.

Клиентские сертификаты

В этой статье рассказывается, как использовать TLS для защиты связи с серверами. TLS также поддерживает сертификаты клиента, которые позволяют серверу проверять личность клиента. Хотя это выходит за рамки данной статьи, используемые методы аналогичны указанию пользовательского TrustManager.

Nogotofail: инструмент для тестирования безопасности сетевого трафика

Nogotofail – это инструмент, который позволяет легко проверить, защищены ли ваши приложения от известных уязвимостей и ошибок в конфигурации TLS/SSL. Это мощный, автоматизированный и масштабируемый инструмент для тестирования сетевой безопасности на любом устройстве, сетевой трафик которого можно направить через него.

Nogotofail можно использовать в трех основных целях:

  • Поиск ошибок и уязвимостей.
  • Проверка исправлений и отслеживание регрессий.
  • Понимание того, какие приложения и устройства генерируют трафик.

Nogotofail работает на устройствах Android, iOS, Linux, Windows, ChromeOS, macOS и любых других, которые вы используете для подключения к интернету. Для настройки параметров и получения уведомлений на Android и Linux доступен клиент, а сам механизм атаки можно развернуть как маршрутизатор, VPN-сервер или прокси-сервер.

Инструмент доступен на странице проекта Nogotofail с открытым исходным кодом.

Обновления SSL и TLS

Android 10

Некоторые браузеры, например Google Chrome, позволяют пользователям выбирать сертификат, когда сервер TLS отправляет запрос сертификата в рамках подтверждения TLS. Начиная с Android 10 объекты KeyChain учитывают параметры издателей и спецификации ключей при вызове KeyChain.choosePrivateKeyAlias(), чтобы показать пользователям запрос на выбор сертификата. В частности, в этом запросе нет вариантов, которые не соответствуют спецификациям сервера.

Если доступных сертификатов нет (например, ни один из них не соответствует спецификации сервера или на устройстве не установлено ни одного сертификата), запрос на выбор сертификата не появляется.

Кроме того, на устройствах с Android 10 и более поздними версиями ОС не требуется блокировка экрана, чтобы импортировать ключи или сертификаты ЦС в объект KeyChain.

TLS 1.3 включен по умолчанию

В Android 10 и более поздних версиях TLS 1.3 включен по умолчанию для всех подключений TLS. Вот несколько важных деталей о нашей реализации TLS 1.3:

  • Наборы шифров TLS 1.3 нельзя настраивать. Поддерживаемые наборы шифров TLS 1.3 всегда включены, если включен протокол TLS 1.3. Попытки отключить их с помощью вызова setEnabledCipherSuites() игнорируются.
  • При согласовании TLS 1.3 объекты HandshakeCompletedListener вызываются до того, как сеансы добавляются в кеш. В TLS 1.2 и других предыдущих версиях эти объекты вызываются после добавления сеансов в кеш.
  • В некоторых ситуациях, когда экземпляры SSLEngine вызывают ошибку SSLHandshakeException в предыдущих версиях Android, в Android 10 и более поздних версиях они вызывают ошибку SSLProtocolException.
  • Режим 0-RTT не поддерживается.

При необходимости вы можете получить SSLContext с отключенным TLS 1.3, вызвав SSLContext.getInstance("TLSv1.2"). Вы также можете включить или отключить версии протокола для каждого подключения, вызвав setEnabledProtocols() для соответствующего объекта.

Сертификаты, подписанные с помощью SHA-1, не считаются доверенными в TLS

В Android 10 сертификаты, использующие алгоритм хеширования SHA-1, не считаются доверенными в подключениях TLS. Корневые центры сертификации не выпускают такие сертификаты с 2016 года, и они больше не считаются доверенными в Chrome и других популярных браузерах.

Попытка подключения к сайту, который использует сертификат с подписью SHA-1, будет неудачной.

Изменения и улучшения в работе KeyChain

Некоторые браузеры, например Google Chrome, позволяют пользователям выбирать сертификат, когда сервер TLS отправляет запрос сертификата в рамках подтверждения TLS. Начиная с Android 10 объекты KeyChain учитывают параметры издателей и спецификации ключей при вызове KeyChain.choosePrivateKeyAlias(), чтобы показать пользователям запрос на выбор сертификата. В частности, в этом запросе нет вариантов, которые не соответствуют спецификациям сервера.

Если доступных для выбора сертификатов нет (например, ни один из них не соответствует спецификации сервера или на устройстве не установлено ни одного сертификата), запрос на выбор сертификата не появляется.

Кроме того, на устройствах с Android 10 и более поздними версиями ОС для импорта ключей или сертификатов ЦС в объект KeyChain не требуется блокировка экрана.

Другие изменения в TLS и криптографии

В Android 10 вступают в силу несколько незначительных изменений в библиотеках TLS и криптографии:

  • Шифры AES/GCM/NoPadding и ChaCha20/Poly1305/NoPadding возвращают более точные размеры буфера из getOutputSize().
  • Набор шифров TLS_FALLBACK_SCSV не используется при попытках подключения с максимальным протоколом TLS 1.2 или выше. Поскольку реализация TLS-серверов была улучшена, мы не рекомендуем использовать резервный вариант TLS-external. Вместо этого мы рекомендуем использовать согласование версии TLS.
  • ChaCha20-Poly1305 – это псевдоним для ChaCha20/Poly1305/NoPadding.
  • Имена хостов с точками в конце не считаются действительными именами хостов SNI.
  • При выборе ключа подписи для ответов сертификата учитывается расширение supported_signature_algorithms в CertificateRequest.
  • Непрозрачные ключи подписи, например из Android Keystore, можно использовать с подписями RSA-PSS в TLS.

Изменения в HTTPS-подключении

Если приложение, работающее на Android 10, передает значение null в setSSLSocketFactory(), возникает ошибка IllegalArgumentException. В предыдущих версиях передача значения null в setSSLSocketFactory() имела тот же эффект, что и передача текущей фабрики по умолчанию.

Android 11

Сокеты SSL по умолчанию используют движок Conscrypt SSL

Реализация SSLSocket по умолчанию в Android основана на Conscrypt. Начиная с Android 11, эта реализация основана на SSLEngine из Conscrypt.