Как сгенерировать SHA256 шестнадцатеричную строку из PEM файла и реализовать привязку открытого ключа корневого сертификата в Swift для iOS 12?

Вопросы и ответы

У меня есть файл PEM с сертификатом ISRG Root X1, который я загрузил с https://letsencrypt.org/certificates/, и я пытаюсь реализовать сертификатный пиннинг в своем iOS приложении. Меня особенно интересует пиннинг открытого ключа, и я нацеливаюсь на iOS 12 и выше.

У меня есть два основных вопроса:

  1. Как я могу сгенерировать шестнадцатичную строку SHA256 из файла PEM?

  2. После того как я получу шестнадцатичную строку SHA256, как я могу реализовать пиннинг открытого ключа корневого сертификата на Swift с использованием URLSession, не полагаясь на внешние библиотеки?

Я буду очень признателен за любую помощь или ресурсы, которые могут прояснить этот вопрос. Заранее спасибо!

Ответ

Как сгенерировать SHA256 hex строку из PEM файла и реализовать публичное ключевое пиннинг сертификата в Swift для iOS 12

Для реализации вашей задачи, мы разберем два основных этапа: генерация SHA256 hex строки из PEM файла и реализация публичного ключевого пиннинга с использованием URLSession.

Шаг 1: Генерация SHA256 hex строки из PEM файла

  1. Сначала нужно прочитать содержимое вашего PEM файла и извлечь из него публичный ключ. PEM формат обычно имеет вид:

    -----BEGIN CERTIFICATE-----
    (base64 encoded data)
    -----END CERTIFICATE-----
  2. Используйте следующий код на Swift для извлечения публичного ключа и генерации SHA256 hex строки:
import Foundation
import Security
import CommonCrypto

func sha256HexString(from pemFile: String) -> String? {
    guard let pemData = try? Data(contentsOf: URL(fileURLWithPath: pemFile)) else {
        print("Не удалось прочитать PEM файл")
        return nil
    }

    // Удаляем строки заголовка и хвоста
    let base64String = pemData
        .string(replacingOccurrencesOf: "-----BEGIN CERTIFICATE-----\n", with: "")
        .replacingOccurrences(of: "-----END CERTIFICATE-----\n", with: "")
        .replacingOccurrences(of: "\n", with: "")

    // Декодируем из Base64
    guard let data = Data(base64Encoded: base64String) else {
        print("Не удалось декодировать Base64")
        return nil
    }

    // Получаем публичный ключ
    var secError: Unmanaged<CFError>?
    guard let certificate = SecCertificateCreateWithData(nil, data as CFData),
          let publicKey = SecCertificateCopyKey(certificate!) else {
        print("Не удалось создать сертификат или получить публичный ключ")
        return nil
    }

    // Получаем данные публичного ключа
    var keyData: CFData?
    var publicKeyData: SecKey?
    let status = SecKeyCopyData(publicKey, &keyData)

    guard status == errSecSuccess, let keyBytes = keyData else {
        print("Не удалось получить данные публичного ключа")
        return nil
    }

    // Генерируем SHA256 хэш
    var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
    keyBytes.copyBytes(to: hash, count: keyBytes.count)
    CC_SHA256(&hash, CC_LONG(keyBytes.count), &hash)

    // Преобразуем хэш в hex строку
    return hash.map { String(format: "%02hhx", $0) }.joined()
}

Шаг 2: Реализация публичного ключевого пиннинга

Теперь, когда у нас есть SHA256 строка публичного ключа, можно перейти к реализации пиннинга:

  1. Настройте URLSession для выполнения запросов и добавления пиннинга:
import Foundation

class APICaller: NSObject, URLSessionDelegate {
    let pinnedPublicKeySHA256: String

    init(pinnedPublicKey: String) {
        self.pinnedPublicKeySHA256 = pinnedPublicKey
    }

    func fetchResource() {
        let url = URL(string: "https://example.com")! // URL вашего сервиса
        let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)

        let task = session.dataTask(with: url) { (data, response, error) in
            // Обработка ответа
            if let error = error {
                print("Ошибка запроса: \(error)")
                return
            }
            // Обработка полученных данных ...
        }
        task.resume()
    }

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

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

            // Получаем сертификат
            let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0)
            guard let serverPublicKey = SecCertificateCopyKey(serverCertificate!) else {
                completionHandler(.performDefaultHandling, nil)
                return
            }

            // Получаем данные публичного ключа
            var keyData: CFData?
            if SecKeyCopyData(serverPublicKey, &keyData) == errSecSuccess, let keyBytes = keyData {
                // Генерируем SHA256 хэш
                var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
                keyBytes.copyBytes(to: hash, count: keyBytes.count)
                CC_SHA256(&hash, CC_LONG(keyBytes.count), &hash)

                let publicKeySHA256 = hash.map { String(format: "%02hhx", $0) }.joined()

                // Сравниваем хэши
                if publicKeySHA256 == self.pinnedPublicKeySHA256 {
                    completionHandler(.useCredential, URLCredential(trust: serverTrust))
                    return
                }
            }
        }

        // Пиннинг не удался, выполняем стандартную обработку
        completionHandler(.performDefaultHandling, nil)
    }
}

Использование

Код выше можно использовать следующим образом:

if let sha256 = sha256HexString(from: "path_to_your_pem_file.pem") {
    let apiCaller = APICaller(pinnedPublicKey: sha256)
    apiCaller.fetchResource()
}

Заключение

С помощью данного подхода вы сможете успешно реализовать публичное ключевое пиннинг в вашем iOS приложении, используя SHA256 хэш из PEM файла. Убедитесь, что вы правильно указали путь к вашему сертификату и протестировали приложение для корректной обработки ситуации, когда пиннинг не срабатывает.

Оцените материал
Добавить комментарий

Капча загружается...