
Одна из санкций, которая досталась России, — запрет на выдачу и продление SSL-сертификатов. Это приводит к тому, что у некоторых компаний сертификат может протухнуть и сайты перестанут открываться.
Основных решений два:
Использовать российский Яндекс.Браузер или Атом.
Поставить на компьютер сертификат или профиль от Минцифры.
Для мобильных приложений это превращается в особую проблему — могут перестать проходить платежи разных эквайрингов.
Например, 15 февраля 2023 года у Сбера истечёт действие сертификата и надо переходить на самоподписанный. Если этого не сделать, то эквайринг через Сбер может перестать работать. SberPay будет работать как и раньше.
В статье покажу, что делать разработчикам приложений, чтобы экраны c 3-D Secure открывались и эквайринг продолжал работу.
Info.plist
На уровне конфигурации проекта ничего менять не нужно. Скорее всего, у вас уже стоит флаг NSAllowsArbitraryLoadsInWebContent в Info.plist: он нужен, чтобы вообще уметь хоть что-то грузить в WKWebView.
<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoadsInWebContent</key> <true/> </dict>
На ревью Apple спросит, зачем это вам — расскажите про 3-D Secure.
Подставляем правильный сертификат
Сертификаты берём отсюда https://www.gosuslugi.ru/crt. Нам понадобится сертификат для macOS в .pem формате.
Увы, это страница для пользователей, а не для разработчиков, поэтому сертификаты придётся привести в другой вид. Важно, что сертификатов два: Russian Trusted Root CA и Russian Trusted Sub CA. Нужны оба.
Чтобы получить их, можно взять .pem файл, разделить его на два и сконвертировать каждый в .der, потому что iOS только с .der умеет работать.
openssl x509 -outform der -in certificate1.pem -out certificate1.der
Если вы почему-то доверяете мне, а не Минцифре, то можете сразу взять готовые:
Добавляйте их в проект, линкуйте так, чтобы они попал в бандл.
Дополнительная проверка сертификата
Если экран не проходит стандартную валидацию сертификатом, зашитым в операционную систему, то в делегате WKWebView вызывается дополнительная проверка подключения. По документации мы должны спросить у пользователя, стоит ли открывать эту страницу или как-то иначе проверить безопасность подключения. Будем разрешать подключение после проверки сертификата от Минцифры.
Поймаем провалившуюся проверку. В челлендже есть serverTrust-объект, по которому мы должны принять решение. Если валидация не прошла, то возвращаемся к дефолтному поведению через .performDefaultHandling
extension ViewController: WKNavigationDelegate { func webView( _ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void ) { guard let serverTrust = challenge.protectionSpace.serverTrust else { return completionHandler(.performDefaultHandling, nil) } Task.detached(priority: .userInitiated) { if await self.validator.checkValidity(of: serverTrust) { // Allow our sertificate let cred = URLCredential(trust: serverTrust) completionHandler(.useCredential, cred) } else { // Default check for another connections completionHandler(.performDefaultHandling, nil) } } } }
Всю работу с сертификатами спрячем в валидатор. Вся работа должна быть в отдельном потоке, поэтому весь код обёрнут в актор для синхронизации значений внутри него. В примере много специфичного кода из фреймворка Security, но его документация все объясняет очень подробно.
actor CertificateValidator { var certificates = [SecCertificate]() func prepareCertificates(_ names: [String]) { certificates = names.compactMap(certificate(name:)) } private func certificate(name: String) -> SecCertificate? { let path = Bundle.main.url(forResource: name, withExtension: "der") let certData = try! Data(contentsOf: path!) let certificate = SecCertificateCreateWithData(nil, certData as CFData) return certificate } func checkValidity(of serverTrust: SecTrust, anchorCertificatesOnly: Bool = false) -> Bool { SecTrustSetAnchorCertificates(serverTrust, certificates as CFArray) SecTrustSetAnchorCertificatesOnly(serverTrust, anchorCertificatesOnly) var error: CFError? let isTrusted = SecTrustEvaluateWithError(serverTrust, &error) return isTrusted } }
Ну и вызовем загрузку сертификатов во viewDidLoad:
class ViewController: UIViewController { let validator = CertificateValidator() override func viewDidLoad() { super.viewDidLoad() Task { let names = ["Russian Trusted Root CA", "Russian Trusted Sub CA"] await validator.prepareCertificates(names) } let url = URL(string: "https://3dsecmt.sberbank.ru/payment/se/keys.do")! webView.navigationDelegate = self webView.load(URLRequest(url: url)) } @IBOutlet weak var webView: WKWebView! }
Полный пример вы можете посмотреть в GitHub. Запускайте проект, сайт должен открыться.
В статье я показал самый простой пример, чтобы была понятна суть, но в руководстве от Сбера вы можете найти полный текст с дополнительными проверками.g
Вопросы
Как это сделать на Andriod?
Посмотрите инструкцию от Сбера
Будут ли проблемы у других банков?
Рано или поздно. Решение прогоняет все запросы через сертификат Минцифры, поэтому должно быть массовым решением.
Что будет с эквайрингами в других странах?
Если мы не прошли проверку по сертификату Минцифры, то передаём контроль поведению по умолчанию, что позволит грузиться и иностранным эквайрингам.
Как это решение влияет на PCI DSS?
Никак не должно влиять, потому что NSAllowsArbitraryLoadsInWebContent и так стоял, а других решений для России нет пока.
Когда надо обновлять сертификат?
Через 5 лет истекает самый короткоживущий. Заведите себе напоминание обновить за год, тогда большинство пользователей бесшовно обновится. Мы себе написали unit-тест который упадет за год до истечения срока сертификата.
Сертификат Минцифры безопасен?
По сути, вы передаёте возможность читать ваш трафик государству. Подходит вам это или нет — решайте сами, у всех разные ситуации. Можно отдать это на откуп пользователям, чтобы они сами поставили нужный профиль, но большинство из них просто не поймут проблему и способ решения, так как столкнутся с этим впервые.
Вижу в логах текст [Security] This method should not be called on the main thread as it may lead to UI unresponsiveness.
Вся работа с фреймворком Security должна быть в фоновом потоке, но видимо где-то в WKWebView это нарушено.
У меня эквайринг подключен через SDK и я не могу поменять код, что делать?
Скорее всего последняя версия SDK уже содержит это исправление. Уточните у своего экваера.
Есть ли решение для UIWebView?
Я такого не нашел
Надеюсь, статья поможет вовремя выпустить обновления для приложений и не потерять в деньгах. Если у вас есть вопросы или вы можете дополнить тему — приходите в комментарии.
Если понравилась статья и хочешь больше узнать о разработке приложений Dodo Brands, подписывайся на наш канал Dodo Mobile.
