Android. Защищенное соединение
Введение
На одном из последних проектов возникла потребность детальнее поразбираться в сертификатах и защищенных соединениях, чтобы локализовать проблему при установлении соединения с тестовым сервером. Так появилась эта статья, в которой разберем, как обезопасить данные Android-приложения при их передаче через Интернет, коснемся протоколов SSL/TLS и узнаем, как правильно настроить HTTPS соединения в Android-приложении.
Что такое SSL/TLS?
SSL (Secure Sockets Layer) и его преемник TLS (Transport Layer Security) обеспечивают защищенное соединение между клиентом и сервером. В Android разработчики могут использовать SSL/TLS для шифрования данных, передаваемых по сети, что особенно важно при взаимодействии с личными данными, платежной информацией и другой конфиденциальной информацией.
Когда требуется особое погружение в тему SSL/TLS:
- Работа с self-signed сертификатами — иногда необходимо разрешить вашему приложению подключаться к серверам с самостоятельно подписанными сертификатами.
- Пиннинг сертификатов — для защиты от атак “Человек посередине” (man in the middle), когда приложение должно принимать только определенные сертификаты.
- Соединение через прокси-серверы, которые могут требовать полностью другой метод проверки сертификатов.
- Требования к алгоритмам шифрования — некоторые приложения или регуляторы могут требовать использования конкретных алгоритмов шифрования.
Почему это важно для Android-приложений?
Android-приложения часто передают конфиденциальные данные и другие чувствительные данные для входа в систему. Последние несколько месяцев я работаю над связанным с банком приложением (хотя само оно к банку отношения не имеет), поэтому в нем оказалось довольно много различных нюансов, связанных с безопасностью, в частности с безопасным сетевым взаимодействием в клиент-серверной архитектуре.
Как работает SSL/TLS?
Процесс установления SSL/TLS соединения известен как “рукопожатие” (handshake). Вот его ключевые шаги:
- Инициализация соединения: Клиент отправляет запрос на установление соединения с сервером.
- Представление сертификата: Сервер предоставляет свой публичный SSL/TLS сертификат.
- Проверка сертификата: Клиент проверяет сертификат через центр сертификации (CA) для подтверждения его достоверности.
- Зашифрованная сессия: После проверки клиент и сервер согласовывают уникальный сессионный ключ для шифрования данных.
Теперь непосредственно к Android части. Рассмотрим пару сценариев использования
Сценарий 1: Работа с self-signed сертификатами
Такие сертификаты не доверенные и могут использоваться для внутренних целей или при разработке. Так как они не выпущены известным центром сертификации (CA), для их использования нужна специальная настройка.
Сценарий 2: Использование Network Security Config для фингерпринтинга сертификатов
Для повышения уровня безопасности можно использовать фингерпринтинг (сверка отпечатков сертификатов) сертификатов в конфигурации Network Security Config, что делает приложение устойчивым к атакам типа “Человек посередине” (man in the middle).
Network Security Config
С Android 7 (24 API) появилась возможность конфиг, который позволяет настраивать политики безопасности сети через XML-конфигурацию в ресурсах приложения, который также декларируется в манифесте.
1
2
3
4
5
6
7
8
9
<network-security-config>
<domain-config>
<domain includeSubdomains="true">host.ru</domain>
<pin-set>
<pin digest="SHA-256">код серта</pin>
<!-- Пины для сертификатов -->
</pin-set>
</domain-config>
</network-security-config>
Этот файл определяет безопасность соединения для конкретных доменов, включая возможность пиннинга сертификатов.
Пример
В примере ниже мы создаем OkHttpClient с пиннингом сертификата, который будет доверять только определенному сертификату. Это особенно полезно при повышении безопасности сетевых запросов и защите от атак “Человек посередине”, и такая схема используется у меня в компании. В примере предполагается, что указанный InputStream certificateInputStream содержит сам сертификат, который мы хотим зафиксировать. Например, в моем случае серты лежат в raw ресурсах проекта.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import java.security.cert.CertificateFactory
fun createOkHttpClientWithPinnedCertificate(certificateInputStream: InputStream): OkHttpClient {
val certificateFactory = CertificateFactory.getInstance("X.509")
// Создадим сертификат из InputStream
val certificate = certificateFactory.generateCertificate(certificateInputStream)
// Подготовка KeyStore, включая наш сертификат
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()).apply {
load(null, null)
setCertificateEntry("ca", certificate)
}
// Инициализируем TrustManagerFactory с KeyStore
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
.apply {
init(keyStore)
}
// Получаем X509TrustManager из TrustManagerFactory
val trustManager = trustManagerFactory.trustManagers[0] as X509TrustManager
// Инициализируем новый SSLContext, используя TrustManager для проверки сертификатов
val sslContext = SSLContext.getInstance("TLS")
.apply {
init(null, arrayOf(trustManager), null)
}
// Создаем OkHttpClient и устанавливаем SSLSocketFactory с нашим sslContext
return OkHttpClient.Builder()
.sslSocketFactory(sslContext.socketFactory, trustManager)
.build()
}
// Использование созданного OkHttpClient для выполнения запроса
fun makeSecureRequest() {
val certificateInputStream: InputStream = /* Загружаем сертификаты */
val client = createOkHttpClientWithPinnedCertificate(certificateInputStream)
val request = Request.Builder()
.url("https://host.ru")
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Exception: ${response.code}")
// Обработка ответа...
}
}
Один из ключевых моментов здесь - это TrustManager, рассмотрим его подробнее. TrustManager в контексте SSL/TLS в Java и Android отвечает за управление доверием для SSL соединений. Это элемент безопасности, который принимает решения о том, следует ли доверять предоставленным в процессе установления соединения сертификатам.
Роль и использование TrustManager:
Проверка Сертификатов: Основная задача TrustManager заключается в проверке цепочки сертификатов, представленной сервером во время установления SSL/TLS соединения. Это обеспечивает, что соединение происходит с тем сервером, на который оно предназначалось, и что сервер является доверенным и подлинным.
Управление Доверием: TrustManager использует хранилище доверенных сертификатов (TrustStore), чтобы решить, доверять ли удаленному серверу. TrustStore обычно содержит сертификаты известных центров сертификации (CA), с помощью которых выполняется валидация представленных сертификатов.
Настраиваемая Логика Доверия: Можно создать свою реализацию TrustManager, если необходимо использовать специальную логику доверия. Например, для приложений, которые должны доверять только определенным сертификатам или которые работают с self-signed сертификатами.
Интеграция с SSLContext: TrustManager должен быть инициализирован и использован для создания SSLContext. SSLContext используется для создания SSLSocketFactory, который уже применяется в сетевых подключениях для обеспечения SSL/TLS защиты.
В качестве заключения
Заключение получилось больше для себя, но все же:) Работая над статьей, лучшие погрузился в тему и нашел ответы на некоторые вопросы, которые были непонятны на проекте. Например, пиннинг сертификатов и Network Security Config оставались чем-то не до конца ясным. Надеюсь, кому-нибудь статья тоже будет полезной.