Инструменты страницы
Добавление собственного сертификата для всего приложения на Android (Network Security Configuration)
Архив для скачивания: 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 без и с добавлением сертификата
Рассмотрим добавление сертификата на простом приложении с 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() }
Пример можно посмотреть во вложении.