Архив для скачивания: v2certificates_android.zip
Важно! Для корректной работы необходимо использовать исключительно WebViewClient, альтернативные методы взаимодействия с веб-интерфейсами не имеют нужного функционала.
Начиная с версии Android 24 (N) в системе исключена возможность доверять сайтам по умолчанию. В связи с этим появилась возможность добавлять собственные сертификаты через конфигурационные файлы.
Более подробно:
1) https://android-developers.googleblog.com/2016/07/changes-to-trusted-certificate.html
2) https://developer.android.com/training/articles/security-config
Рассмотрим добавление сертификата на простом приложении с WebView. В качестве тестового домена был выбран URL:
https://3dsec.sberbank.ru/payment/webservices/merchant-ws?wsdl.
Если попытаться загрузить страницу через WebView без добавления сертификата, то в методе onReceivedSslError класса WebViewClient вернется ошибка SSL_UNTRUSTED (1).
1) Для успешной загрузки страницы, необходимо выполнить следующие шаги для добавления сертификата.
A) В AndroidManifest добавить следующую строчку (доступна с версии API 24,N).
<application
android:networkSecurityConfig="@xml/network_security_config"/>
<uses-permission android:name="android.permission.INTERNET" />
……..
B) Создать каталог raw с файлом сертификата (сертификат должен быть в РЕМ формате).
C) Далее необходимо создать файл network_security_config.xml в директории xml.
Содержимое файла приведено ниже.
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">3dsec.sberbank.ru</domain>
<trust-anchors>
<certificates src="@raw/root"/>
</trust-anchors>
</domain-config>
</network-security-config>
Вы можете убрать опцию domain, и тогда сертификат будет работать для любых доменов на уровне всего приложения.
Если хотим оставить как в примере выше, то убедитесь, что для промышленной сборки, будет добавлен домен: https://securepayments.sberbank.ru.
D) После запуска приложения страница загрузится в WebView без ошибки SSL_UNTRUSTED.
P.S. Корневой сертификат Минцифр из примера был скачан с сайта (Сертификат для Android): https://www.gosuslugi.ru/cr
Также возможен вариант с ручной проверкой сертификатов при первой попытке загрузки страницы в WebView. Для этого необходимо:
1) Подготовить TrustManager.
private fun fromInputStream(caInput: InputStream): TrustManagerFactory {
val cf: CertificateFactory = CertificateFactory.getInstance("X.509")
val ca = caInput.use { cf.generateCertificate(it) }
val keyStore: KeyStore = KeyStore.getInstance(KeyStore.getDefaultType()).apply {
load(null, null)
setCertificateEntry("ca", ca)
}
val tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm()
val tmf = TrustManagerFactory.getInstance(tmfAlgorithm)
tmf.init(keyStore)
return tmf
}
2) Реализовать собственный WebViewClient с предопределенным методом onReceivedSslError.
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) {
Log.d("WEB_VIEW_EXAMPLE", "onReceivedSslError")
var passVerify = false
if (error.primaryError == SslError.SSL_UNTRUSTED) {
val cert = error.certificate
val subjectDN = cert.issuedTo.dName
Log.d("WEB_VIEW_EXAMPLE", "subjectDN: $subjectDN")
try {
val f: Field = cert.javaClass.getDeclaredField("mX509Certificate")
f.setAccessible(true)
val x509 = f.get(cert) as X509Certificate
val chain = arrayOf(x509)
for (trustManager in tmf.getTrustManagers()) {
if (trustManager is X509TrustManager) {
val x509TrustManager: X509TrustManager = trustManager as X509TrustManager
try {
x509TrustManager.checkServerTrusted(chain, "generic")
passVerify = true
break
} catch (e: Exception) {
Log.e("WEB_VIEW_EXAMPLE", "verify trustManager failed", e)
passVerify = false
}
}
}
Log.d("WEB_VIEW_EXAMPLE", "passVerify: $passVerify")
} catch (e: Exception) {
Log.e("WEB_VIEW_EXAMPLE", "verify cert fail", e)
}
}
if (passVerify == true) handler.proceed() else handler.cancel()
}
Пример можно посмотреть во вложении.