Pull to refresh
Dodo Engineering
О том, как разработчики строят IT в Dodo

Как подключить российский SSL-сертификат к iOS-приложению

Reading time 5 min
Views 10K

Одна из санкций, которая досталась России, — запрет на выдачу и продление SSL-сертификатов. Это приводит к тому, что у некоторых компаний сертификат может протухнуть и сайты перестанут открываться.

Основных решений два:

  1. Использовать российский Яндекс.Браузер или Атом.

  2. Поставить на компьютер сертификат или профиль от Минцифры.

Для мобильных приложений это превращается в особую проблему — могут перестать проходить платежи разных эквайрингов.

Например, 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

Если вы почему-то доверяете мне, а не Минцифре, то можете сразу взять готовые:

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 лет истекает самый короткоживущий. Заведите себе напоминание обновить за год, тогда большинство пользователей бесшовно обновится. Мы себе написали 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.

Tags:
Hubs:
+8
Comments 21
Comments Comments 21

Articles

Information

Website
dodo.dev
Registered
Founded
Employees
201–500 employees
Location
Россия