Certificate Pinning — механізм фіксації сертифіката або відкритого ключа сервера, при якому додаток використовує заздалегідь відомий відбиток для перевірки HTTPS-з'єднання. На відміну від стандартного ланцюжка довіри через CA, pinning гарантує, що навіть скомпрометований центр сертифікації не зможе видати підроблений сертифікат для вашого домену. Згідно з OWASP MSTG (2025), Certificate Pinning входить до списку обов'язкових контролів для додатків рівня захисту L2. Реалізація включає зберігання хешів сертифікатів у коді та перевірку при кожному запиті.
Головне
Certificate Pinning — це техніка безпеки, при якій додаток зберігає відбиток (fingerprint) довіреного сертифіката і використовує його як єдиний критерій для встановлення HTTPS-з'єднання. У стандартній моделі TLS клієнт перевіряє, що сертифікат сервера підписаний довіреним кореневим CA — будь-яким із сотень попередньо встановлених у системі центрів. Certificate Pinning замінює цей ланцюжок на пряму перевірку: сертифікат повинен збігатися зі збереженим зразком або містити очікуваний відкритий ключ.
Проблема стандартної моделі стала очевидною після інцидентів з компрометацією CA — DigiNotar (2011), Comodo (2011), TrustCor (2022). Якщо CA випускає підроблений сертифікат для вашого домену, браузер або додаток приймає його як валідний. Certificate Pinning запобігає цій атаці: навіть ідеально підписаний підроблений сертифікат буде відхилено, оскільки його відбиток не збігається із зафіксованим у додатку.
Термін pinning походить від pin — «штифт» або «фіксатор»: розробник фіксує довірений сертифікат, і будь-яке відхилення від нього блокує з'єднання. За даними дослідження Mitre CWE-295, improper certificate validation залишається однією з top-10 найбільш небезпечних помилок безпеки в мобільних додатках, і Certificate Pinning є прямим методом її запобігання.
Спочатку Certificate Pinning використовувався в браузерах через механізм HPKP (HTTP Public Key Pinning), стандартизований у RFC 7469. Розробник надсилав HTTP-заголовок Public-Key-Pins з хешами очікуваних ключів, і браузер запам'ятовував їх на вказаний термін. Однак HPKP виявився небезпечним: одна помилка в конфігурації могла заблокувати сайт на місяці. У 2018 році Chrome припинив підтримку HPKP, і зараз стандартом стала програмна реалізація на стороні клієнта — всередині мобільного додатка або розширення браузера.
Процес Certificate Pinning включає три ключові етапи: обчислення відбитка, перевірка при з'єднанні та обробка помилки. На етапі підготовки розробник отримує SHA-256 хеш сертифіката або відкритого ключа продакшен-сервера. Для GDPR- та PCI DSS-сумісних додатків також потрібно зафіксувати відбитки проміжних CA в ланцюжку.
При кожному HTTPS-запиті додаток перехоплює callback аутентифікації TLS, витягує сертифікат сервера і обчислює його SHA-256 хеш. Цей хеш порівнюється зі збереженим списком довірених відбитків. Якщо збіг знайдено — з'єднання продовжується. Якщо ні — додаток повинен розірвати з'єднання і повідомити про помилку, не розкриваючи деталей реалізації зловмиснику.
fun validateCertificate(certificate: X509Certificate,
expectedHash: String): Boolean {
val digest = MessageDigest.getInstance("SHA-256")
val hash = Base64.encodeToString(
digest.digest(certificate.publicKey.getEncoded()),
Base64.DEFAULT
).trim()
return hash == expectedHash
}
Функція приймає об'єкт X509Certificate від сервера та очікуваний хеш. Спочатку витягується відкритий ключ сертифіката, обчислюється SHA-256 хеш і кодується в Base64. Результат порівнюється з очікуваним відбитком. У продакшені варто додати перевірку по масиву з 2–3 відбитків для підтримки ротації.
При реалізації pinning потрібно вибрати, який саме криптографічний об'єкт фіксувати. Certificate Pinning прив'язується до самого сертифіката X.509 — його серійного номеру, терміну дії та всього ланцюжка. Public Key Pinning фіксує тільки відкритий ключ всередині сертифіката, ігноруючи інші поля. Вибір суттєво впливає на операційні витрати.
| Критерій | Certificate Pinning | Public Key Pinning |
|---|---|---|
| Об'єкт фіксації | Сертифікат X.509 цілком | Відкритий ключ RSA/ECDSA |
| Ротація | Вимагає оновлення при кожному перевипуску | Не змінюється при зміні сертифіката з тим же ключем |
| Безпека | Максимально точна прив'язка | Менш чутливий до деталей |
| Гнучкість | Низька — сертифікати змінюються кожен 1–2 роки | Висока — ключі можуть жити 5–10 років |
| Рекомендація | Для критичних систем з контрольованими оновленнями | Для більшості мобільних додатків та API |
Public Key Pinning — найкращий вибір для більшості проектів. Відкриті ключі серверів зазвичай залишаються незмінними при перевипуску сертифіката — компанія просто підписує старий ключ новим сертифікатом. Це означає, що додаток не потребує оновлення після зміни сертифіката, якщо ключова пара не змінювалася. Certificate Pinning ж рекомендований для сценаріїв, де розробник повністю контролює і сервер, і клієнтський код, наприклад, у корпоративних додатках з жорстким циклом оновлення.
TOFU — стратегія, при якій Certificate Pinning не налаштовується заздалегідь, а запам'ятовує сертифікат при першому з'єднанні з сервером. Цей підхід зручний для додатків, які не знають заздалегідь, до якого сервера будуть підключатися. Недолік — вразливість при першій атаці: якщо перше з'єднання перехоплено, підроблений сертифікат буде прийнято як довірений. TOFU застосовується в SSH-з'єднаннях і деяких P2P-протоколах.
На обох платформах Certificate Pinning реалізується через перехоплення TLS-з'єднання на рівні мережевого стека. На iOS використовується делегат URLSession або Alamofire ServerTrustManager. На Android найкращий спосіб — OkHttp CertificatePinner, який вбудований у популярні HTTP-клієнти і підтримує конфігурацію кількох відбитків для кожного домену.
func validate(serverTrust: SecTrust,
pinnedHash: String) -> Bool {
guard let certificates = SecTrustCopyCertificateChain(serverTrust)
as? [SecCertificate] else { return false }
for certificate in certificates {
let data = SecCertificateCopyData(certificate)
var hash = Data(repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
data.withUnsafeBytes {
CC_SHA256($0.baseAddress,
CC_LONG(data.count), &hash)
}
if hash.base64EncodedString() == pinnedHash {
return true
}
}
return false
}
У Swift-функції з serverTrust витягується ланцюжок сертифікатів, для кожного обчислюється SHA-256 хеш, і результат порівнюється з очікуваним. Прохід по всіх сертифікатах у ланцюжку дозволяє реалізувати pinning на рівні проміжного CA — якщо проміжний сертифікат збігається, з'єднання приймається. Це дає гнучкість при ротації leaf-сертифікатів.
Якщо додаток не використовує OkHttp, Certificate Pinning можна реалізувати через кастомний X509TrustManager. Цей метод вимагає більше коду, але дає повний контроль над процесом перевірки. TrustManager перевизначає метод checkServerTrusted, де розробник вручну перевіряє сертифікати сервера і приймає рішення про довіру. Рекомендується тільки для специфічних сценаріїв, де бібліотека OkHttp недоступна.
Найпоширеніша помилка — відсутність backup pins. Розробник закладає один відбиток сертифіката, і при його закінченні користувачі масово втрачають з'єднання. Мінімально допустима конфігурація — два відбитки: поточний сертифікат і резервний. Оптимально — три: поточний, резервний і відбиток кореневого CA як fallback.
Друга помилка — зберігання pins у відкритому вигляді в коді. Зловмисник з доступом до APK або IPA може легко витягти відбитки і підмінити їх. Рекомендується обфускація хешів: розбити рядок на частини, зберігати в ресурсах з шифруванням або обчислювати через runtime. Для Android ефективний ProGuard з обфускацією рядкових констант.
Третя помилка — піннінг на рівні development-сертифіката. Сертифікати розробки та продакшену зазвичай різні, але розробники часто забувають переключити pins при збірці релізу. Результат — продакшен-додаток не може з'єднатися з сервером. Рішення — роздільна конфігурація pins для debug та release через BuildConfig або flavour-специфічні ресурси.
Поширені запитання
SSL Pinning — загальний термін для прив'язки до SSL/TLS сертифіката. Certificate Pinning — конкретна реалізація, що фіксує сам сертифікат X.509, а не тільки відкритий ключ. Різниця в об'єкті прив'язки: сертифікат vs ключ.
Рекомендується зберігати хеші в ресурсах з обфускацією через ProGuard (Android) або зашифрованими через Keychain (iOS). Уникайте зберігання pins у відкритому вигляді в strings.xml або Info.plist без шифрування.
При кожній зміні сертифіката на сервері. Рекомендується додавати новий відбиток як backup pin за 3–6 місяців до закінчення поточного, а після ротації видаляти старий. Мінімум один backup pin обов'язковий.
Так, через умовну компіляцію: в debug-збірці pinning вимкнено, в release — увімкнено. Використовуйте BuildConfig.DEBUG на Android або #if DEBUG на iOS для перемикання. Ніколи не робіть це через runtime-флаг, доступний користувачу.
Негайно випустити оновлення додатка з новими відбитками та опублікувати його в сторах. Використовувати механізм форсованого оновлення. Якщо backup pins включали відбиток резервного CA, тимчасово можна переключитися на інший домен з іншим сертифікатом.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також