Как подключить российский SSL-сертификат к iOS-приложению
Одна из санкций, которая досталась России, — запрет на выдачу и продление SSL-сертификатов. Это приводит к тому, что у некоторых компаний сертификат может протухнуть и сайты перестанут открываться.
Основных решений два:
Использовать российский Яндекс.Браузер или Атом.
Поставить на компьютер сертификат или профиль от Минцифры.
Для мобильных приложений это превращается в особую проблему — могут перестать проходить платежи разных эквайрингов.
Например, 15 февраля 2023 года у Сбера истечёт действие сертификата и надо переходить на самоподписанный. Если этого не сделать, то эквайринг через Сбер может перестать работать. SberPay будет работать как и раньше.
В статье покажу, что делать разработчикам приложений, чтобы экраны c 3-D Secure открывались и эквайринг продолжал работу.
Info.plist
На уровне конфигурации проекта ничего менять не нужно. Скорее всего, у вас уже стоит флаг NSAllowsArbitraryLoadsInWebContent
в Info.plist
: он нужен, чтобы вообще уметь хоть что-то грузить в WKWebView
.
NSAppTransportSecurity
NSAllowsArbitraryLoadsInWebContent
На ревью 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
Если вы почему-то доверяете мне, а не Минцифре, то можете сразу взять готовые:
Russian Trusted Sub CA.der
Russian Trusted Root CA.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 лет истекает самый короткоживущий. Заведите себе напоминание обновить за год, тогда большинство пользователей бесшовно обновится.
Сертификат Минцифры безопасен?
По сути, вы передаёте возможность читать ваш трафик государству. Подходит вам это или нет — решайте сами, у всех разные ситуации. Можно отдать это на откуп пользователям, чтобы они сами поставили нужный профиль, но большинство из них просто не поймут проблему и способ решения, так как столкнутся с этим впервые.
Вижу в логах текст [Security] This method should not be called on the main thread as it may lead to UI unresponsiveness.
Вся работа с фреймворком Security
должна быть в фоновом потоке, но видимо где-то в WKWebView
это нарушено.
У меня эквайринг подключен через SDK и я не могу поменять код, что делать?
Скорее всего последняя версия SDK уже содержит это исправление. Уточните у своего экваера.
Надеюсь, статья поможет вовремя выпустить обновления для приложений и не потерять в деньгах. Если у вас есть вопросы или вы можете дополнить тему — приходите в комментарии.
Если понравилась статья и хочешь больше узнать о разработке приложений Dodo Brands, подписывайся на наш канал Dodo Mobile.