Как работать с самоподписанными SSL-сертификатами в iOS?

«Как работать с самоподписанными SSL-сертификатами в iOS?» — вопрос из категории Сети, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Работа с самоподписанными сертификатами требует настройки URLSession для обработки SSL-рукопожатия в обход стандартной проверки доверенных центров сертификации (CA).

Основной подход — использование делегата URLSession:

  1. Реализовать метод urlSession(_:didReceive:completionHandler:).
  2. Принять (useCredential) или отклонить (cancelAuthenticationChallenge) сертификат на основе кастомной логики.

Пример: Принятие конкретного самоподписанного сертификата из бандла

class SSLPinningDelegate: NSObject, URLSessionDelegate {
    func urlSession(_ session: URLSession,
                    didReceive challenge: URLAuthenticationChallenge,
                    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

        guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
              let serverTrust = challenge.protectionSpace.serverTrust else {
            completionHandler(.performDefaultHandling, nil)
            return
        }

        // 1. Загружаем локальный сертификат
        guard let certPath = Bundle.main.path(forResource: "my_server", ofType: "der"),
              let localCertData = try? Data(contentsOf: URL(fileURLWithPath: certPath)) else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }

        // 2. Извлекаем удаленный сертификат
        let remoteCert = SecTrustGetCertificateAtIndex(serverTrust, 0)
        let remoteCertData = SecCertificateCopyData(remoteCert!) as Data

        // 3. Сравниваем данные сертификатов (пиннинг)
        if localCertData == remoteCertData {
            let credential = URLCredential(trust: serverTrust)
            completionHandler(.useCredential, credential)
        } else {
            completionHandler(.cancelAuthenticationChallenge, nil)
        }
    }
}

// Использование
let delegate = SSLPinningDelegate()
let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)

Ключевые моменты и предупреждения:

  • ⚠️ Безопасность: Такой подход ослабляет защиту и должен использоваться только для тестирования (dev/staging) или внутри защищенных корпоративных сетей.
  • Пиннинг (Pinning): Для продакшена рекомендуется пиннинг публичных ключей или сертификатов от доверенных CA, а не использование самоподписанных.
  • Info.plist: Для iOS 9+ может потребоваться отключение ATS (NSAppTransportSecurity) для доменов с такими сертификатами, что также является плохой практикой.