У меня есть файл PEM с сертификатом ISRG Root X1, который я загрузил с https://letsencrypt.org/certificates/, и я пытаюсь реализовать сертификатный пиннинг в своем iOS приложении. Меня особенно интересует пиннинг открытого ключа, и я нацеливаюсь на iOS 12 и выше.
У меня есть два основных вопроса:
-
Как я могу сгенерировать шестнадцатичную строку SHA256 из файла PEM?
-
После того как я получу шестнадцатичную строку SHA256, как я могу реализовать пиннинг открытого ключа корневого сертификата на Swift с использованием URLSession, не полагаясь на внешние библиотеки?
Я буду очень признателен за любую помощь или ресурсы, которые могут прояснить этот вопрос. Заранее спасибо!
Ответ
Как сгенерировать SHA256 hex строку из PEM файла и реализовать публичное ключевое пиннинг сертификата в Swift для iOS 12
Для реализации вашей задачи, мы разберем два основных этапа: генерация SHA256 hex строки из PEM файла и реализация публичного ключевого пиннинга с использованием URLSession
.
Шаг 1: Генерация SHA256 hex строки из PEM файла
-
Сначала нужно прочитать содержимое вашего PEM файла и извлечь из него публичный ключ. PEM формат обычно имеет вид:
-----BEGIN CERTIFICATE----- (base64 encoded data) -----END CERTIFICATE-----
- Используйте следующий код на 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 строка публичного ключа, можно перейти к реализации пиннинга:
- Настройте
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 файла. Убедитесь, что вы правильно указали путь к вашему сертификату и протестировали приложение для корректной обработки ситуации, когда пиннинг не срабатывает.