Certificate Pinning — mechanizm mocowania certyfikatu lub klucza publicznego serwera, w którym aplikacja używa wcześniej znanego odcisku do weryfikacji połączenia HTTPS. W przeciwieństwie do standardowego łańcucha zaufania przez CA, pinning gwarantuje, że nawet skompromitowany urząd certyfikacji nie będzie mógł wydać podstawionego certyfikatu dla Twojej domeny. Według OWASP MSTG (2025), Certificate Pinning znajduje się na liście obowiązkowych kontroli dla aplikacji o poziomie ochrony L2. Implementacja obejmuje przechowywanie skrótów certyfikatów w kodzie i weryfikację przy każdym żądaniu.
Najważniejsze
Certificate Pinning — to technika bezpieczeństwa, w której aplikacja przechowuje odcisk (fingerprint) zaufanego certyfikatu i używa go jako jedynego kryterium do ustanowienia połączenia HTTPS. W standardowym modelu TLS klient sprawdza, czy certyfikat serwera jest podpisany przez zaufany główny CA — dowolny z setek wstępnie zainstalowanych w systemie urzędów. Certificate Pinning zastępuje ten łańcuch bezpośrednim sprawdzeniem: certyfikat musi pasować do zapisanego wzorca lub zawierać oczekiwany klucz publiczny.
Problem standardowego modelu stał się oczywisty po incydentach z kompromitacją CA — DigiNotar (2011), Comodo (2011), TrustCor (2022). Jeśli CA wydaje podstawiony certyfikat dla Twojej domeny, przeglądarka lub aplikacja akceptuje go jako ważny. Certificate Pinning zapobiega temu atakowi: nawet idealnie podpisany podstawiony certyfikat zostanie odrzucony, ponieważ jego odcisk nie pasuje do ustalonego w aplikacji.
Termin pinning pochodzi od pin — „kołek” lub „mocowanie”: programista mocuje zaufany certyfikat, a każde odstępstwo od niego blokuje połączenie. Według badań Mitre CWE-295, niewłaściwa walidacja certyfikatu pozostaje jednym z top-10 najniebezpieczniejszych błędów bezpieczeństwa w aplikacjach mobilnych, a Certificate Pinning jest bezpośrednią metodą jej zapobiegania.
Początkowo Certificate Pinning był używany w przeglądarkach poprzez mechanizm HPKP (HTTP Public Key Pinning), standaryzowany w RFC 7469. Programista wysyłał nagłówek HTTP Public-Key-Pins ze skrótami oczekiwanych kluczy, a przeglądarka zapamiętywała je na określony czas. Jednak HPKP okazał się niebezpieczny: jeden błąd w konfiguracji mógł zablokować stronę na miesiące. W 2018 roku Chrome zakończył wsparcie dla HPKP i obecnie standardem stała się implementacja programowa po stronie klienta — wewnątrz aplikacji mobilnej lub rozszerzenia przeglądarki.
Proces Certificate Pinning obejmuje trzy kluczowe etapy: obliczanie odcisku, weryfikację przy połączeniu i obsługę błędu. Na etapie przygotowania programista pobiera skrót SHA-256 certyfikatu lub klucza publicznego serwera produkcyjnego. Dla aplikacji zgodnych z GDPR i PCI DSS wymagane jest również ustalenie odcisków pośrednich CA w łańcuchu.
Przy każdym żądaniu HTTPS aplikacja przechwytuje callback uwierzytelniania TLS, wyodrębnia certyfikat serwera i oblicza jego skrót SHA-256. Ten skrót jest porównywany z zapisaną listą zaufanych odcisków. Jeśli znaleziono zgodność — połączenie jest kontynuowane. Jeśli nie — aplikacja musi zerwać połączenie i zgłosić błąd, nie ujawniając szczegółów implementacji atakującemu.
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
}
Funkcja przyjmuje obiekt X509Certificate od serwera i oczekiwany skrót. Najpierw wyodrębniany jest klucz publiczny certyfikatu, obliczany skrót SHA-256 i kodowany w Base64. Wynik jest porównywany z oczekiwanym odciskiem. W produkcji warto dodać sprawdzenie względem tablicy 2–3 odcisków dla wsparcia rotacji.
Przy implementacji pinning należy wybrać, który konkretnie obiekt kryptograficzny mocować. Certificate Pinning przywiązuje się do samego certyfikatu X.509 — jego numeru seryjnego, okresu ważności i całego łańcucha. Public Key Pinning mocuje tylko klucz publiczny wewnątrz certyfikatu, ignorując pozostałe pola. Wybór znacząco wpływa na koszty operacyjne.
| Kryterium | Certificate Pinning | Public Key Pinning |
|---|---|---|
| Obiekt mocowania | Certyfikat X.509 w całości | Klucz publiczny RSA/ECDSA |
| Rotacja | Wymaga aktualizacji przy każdym ponownym wydaniu | Nie zmienia się przy zmianie certyfikatu z tym samym kluczem |
| Bezpieczeństwo | Maksymalnie precyzyjne mocowanie | Mniej wrażliwy na szczegóły |
| Elastyczność | Niska — certyfikaty zmieniają się co 1–2 lata | Wysoka — klucze mogą żyć 5–10 lat |
| Zalecenie | Dla systemów krytycznych z kontrolowanymi aktualizacjami | Dla większości aplikacji mobilnych i API |
Public Key Pinning — preferowany wybór dla większości projektów. Klucze publiczne serwerów zwykle pozostają niezmienione przy ponownym wydaniu certyfikatu — firma po prostu podpisuje stary klucz nowym certyfikatem. Oznacza to, że aplikacja nie wymaga aktualizacji po zmianie certyfikatu, jeśli para kluczy się nie zmieniła. Certificate Pinning jest natomiast zalecany dla scenariuszy, gdzie programista w pełni kontroluje zarówno serwer, jak i kod kliencki, na przykład w aplikacjach korporacyjnych ze ścisłym cyklem aktualizacji.
TOFU — strategia, w której Certificate Pinning nie jest konfigurowany z góry, ale zapamiętuje certyfikat przy pierwszym połączeniu z serwerem. To podejście jest wygodne dla aplikacji, które nie wiedzą z góry, z jakim serwerem będą się łączyć. Wadą jest podatność na pierwszy atak: jeśli pierwsze połączenie zostało przechwycone, podstawiony certyfikat zostanie zaakceptowany jako zaufany. TOFU jest stosowane w połączeniach SSH i niektórych protokołach P2P.
Na obu platformach Certificate Pinning jest implementowany przez przechwycenie połączenia TLS na poziomie stosu sieciowego. Na iOS używany jest delegat URLSession lub Alamofire ServerTrustManager. Na Android preferowanym sposobem jest OkHttp CertificatePinner, który jest wbudowany w popularne klienty HTTP i obsługuje konfigurację wielu odcisków dla każdej domeny.
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
}
W funkcji Swift z serverTrust wyodrębniany jest łańcuch certyfikatów, dla każdego obliczany jest skrót SHA-256, a wynik porównywany z oczekiwanym. Przejście przez wszystkie certyfikaty w łańcuchu pozwala zaimplementować pinning na poziomie pośredniego CA — jeśli pośredni certyfikat pasuje, połączenie jest akceptowane. Daje to elastyczność przy rotacji certyfikatów leaf.
Jeśli aplikacja nie używa OkHttp, Certificate Pinning można zaimplementować przez niestandardowy X509TrustManager. Ta metoda wymaga więcej kodu, ale daje pełną kontrolę nad procesem weryfikacji. TrustManager nadpisuje metodę checkServerTrusted, gdzie programista ręcznie sprawdza certyfikaty serwera i podejmuje decyzję o zaufaniu. Zalecane tylko dla specyficznych scenariuszy, gdzie biblioteka OkHttp jest niedostępna.
Najczęstszym błędem jest brak backup pins. Programista umieszcza jeden odcisk certyfikatu, a po jego wygaśnięciu użytkownicy masowo tracą połączenie. Minimalnie dopuszczalna konfiguracja to dwa odciski: bieżący certyfikat i zapasowy. Optymalnie — trzy: bieżący, zapasowy i odcisk głównego CA jako fallback.
Drugim błędem jest przechowywanie pinów w otwartej postaci w kodzie. Atakujący z dostępem do APK lub IPA może łatwo wyodrębnić odciski i je podmienić. Zaleca się obfuskację skrótów: podzielenie stringa na części, przechowywanie w zasobach z szyfrowaniem lub obliczanie przez runtime. Dla Android skuteczny jest ProGuard z obfuskacją stałych stringowych.
Trzecim błędem jest pinning na poziomie certyfikatu developerskiego. Certyfikaty deweloperskie i produkcyjne są zwykle różne, ale programiści często zapominają przełączyć piny przy budowaniu wydania. Rezultat — aplikacja produkcyjna nie może połączyć się z serwerem. Rozwiązanie — oddzielna konfiguracja pinów dla debug i release przez BuildConfig lub zasoby specyficzne dla wariantu.
Często zadawane pytania
SSL Pinning — ogólny termin dla przywiązania do certyfikatu SSL/TLS. Certificate Pinning — konkretna implementacja, mocująca sam certyfikat X.509, a nie tylko klucz publiczny. Różnica w obiekcie mocowania: certyfikat vs klucz.
Zaleca się przechowywanie skrótów w zasobach z obfuskacją przez ProGuard (Android) lub zaszyfrowane przez Keychain (iOS). Unikaj przechowywania pinów w otwartej postaci w strings.xml lub Info.plist bez szyfrowania.
Przy każdej zmianie certyfikatu na serwerze. Zaleca się dodanie nowego odcisku jako backup pin na 3–6 miesięcy przed wygaśnięciem bieżącego, a po rotacji usunięcie starego. Minimum jeden backup pin jest obowiązkowy.
Tak, przez kompilację warunkową: w buildzie debug pinning jest wyłączony, w release — włączony. Użyj BuildConfig.DEBUG na Android lub #if DEBUG na iOS do przełączania. Nigdy nie rób tego przez flagę runtime dostępną dla użytkownika.
Natychmiast wydać aktualizację aplikacji z nowymi odciskami i opublikować ją w sklepach. Użyć mechanizmu wymuszonej aktualizacji. Jeśli backup pins zawierały odcisk zapasowego CA, tymczasowo można przełączyć się na inną domenę z innym certyfikatem.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również