После обновления SSL сертификата сервера, перестала работать интеграция между web-сервером и мобильным Android-приложением. При дебаге обнаружили ошибку Trust anchor for certification path not found. В статье расскажем как решить проблему. На входе имеем Android приложение на движке LibGDX, Android Studio для разработки, библиотеку OkHttp для обменов с сервером, web-сервер.
Проблема\Предисловие
Проблема возникла в приложении Фрупико Мы используем обмен с сервером в этой игре для сохранения резульатов и демонстрации таблицы рейтинга. Для организации обменов с сервером используем OkHttp. Когда выкатывали первые релизы всё работало. Что же пошло не так? Ранее SSL сертификаты для web-сервера мы генерировали через сервис Let’s Encrypt, которые необходимо было обновлять каждые три месяца. По двум причинам было принято решение перейти на платные сертификаты Global Sign:
- Обновляются раз в год;
- Платный сертификат, вероятно, должен вызывать больше доверия у клиентов, чем бесплатный 🙂
Спустя какое-то время после установки сертификата Global Sign обнаружили, что приложение Фрупико перестало возвращать ответы от сервера (пустые таблицы рейтинга и всплывающие окна).
При дебаге обнаружили ошибку Trust anchor for certification path not found
Ошибка связана с тем, что OkHttp по какой-то причине не доверяет сертификатам Global Sign установленным на сервере.
Решение
1. Скачать AlphaSSL Intermediate Certificates
Зайти на сайт Global Sign и скачать корневые сертификаты. Вы должны получить два файла:
alphasslcasha256g4.crt
gsgccr6alphasslca2023.crt
2. Скопировать сертификаты в папку Assets
3. Добавить метод createSecureHttpClient():
private OkHttpClient createSecureHttpClient() throws Exception {
// Используем getAssets() вместо Gdx.files.internal()
InputStream certStream1 = getAssets().open("alphasslcasha256g4.crt");
InputStream certStream2 = getAssets().open("gsgccr6alphasslca2023.crt");
// Создаем фабрику сертификатов
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// Создаем KeyStore и добавляем сертификаты
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("alphassl-g4", cf.generateCertificate(certStream1));
keyStore.setCertificateEntry("alphassl-2023", cf.generateCertificate(certStream2));
certStream1.close();
certStream2.close();
// Создаем TrustManagerFactory
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
// Настраиваем SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), new java.security.SecureRandom());
// Создаем OkHttpClient с кастомным SSL
return new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory(), (javax.net.ssl.X509TrustManager) tmf.getTrustManagers()[0])
.build();
}
Немного о том, что делает этот код:
- Загружает сертификаты из assets
- getAssets().open(«alphasslcasha256g4.crt»)
- getAssets().open(«gsgccr6alphasslca2023.crt»)
- Создает KeyStore и добавляет туда сертификаты
- KeyStore — это хранилище сертификатов. Добавляем в него alphassl-g4 и alphassl-2023.
- Создает TrustManagerFactory
- Доверяет только сертификатам из KeyStore.
- Настраивает SSLContext с кастомными сертификатами
- SSLContext нужен для безопасного соединения. init(null, tmf.getTrustManagers(), new SecureRandom()) подключает TrustManagerFactory.
- Создает OkHttpClient с этим SSLContext
- sslSocketFactory(sslContext.getSocketFactory(), trustManager)
Теперь клиент доверяет нашим сертификатам и может подключаться к серверу по HTTPS. Осталось только переписать метод создания объекта client:
try {
client = createSecureHttpClient(); // Создаем защищенный клиент OkHttp
} catch (Exception e) {
e.printStackTrace();
client = new OkHttpClient(); // Если ошибка — используем стандартный клиент
}
На этом всё. Спасибо, что читаете Компьюпико.ком! Оставляйте комментарии, смотрите наш канал на YouTube, играйте в наши игры, используйте наши приложения. Удачи в разработке!