Certificate pinning усиливает безопасность SSL/TLS, валидируя, что сертификат сервера совпадает с известным, доверенным сертификатом. Это предотвращает man-in-the-middle (MITM) атаки даже когда trust store устройства скомпрометирован.
Для комплексного обзора основ мобильной безопасности смотрите наше руководство по Тестированию Мобильной Безопасности.
Android Certificate Pinning
OkHttp CertificatePinner
import okhttp3.CertificatePinner
val certificatePinner = CertificatePinner.Builder()
.add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.add("api.example.com", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=") // Backup pin
.build()
val client = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
Генерация Certificate Pins
# Извлечь сертификат с сервера
openssl s_client -connect api.example.com:443 -servername api.example.com < /dev/null \
| openssl x509 -outform DER > cert.der
# Сгенерировать SHA-256 хэш
openssl x509 -in cert.der -inform DER -pubkey -noout \
| openssl pkey -pubin -outform DER \
| openssl dgst -sha256 -binary \
| base64
Тестирование Certificate Pinning
class CertificatePinningTest {
@Test
fun testValidCertificate() = runTest {
val client = createClientWithPinning()
val response = client.newCall(
Request.Builder()
.url("https://api.example.com/users")
.build()
).execute()
assertTrue(response.isSuccessful)
}
@Test
fun testInvalidCertificate() = runTest {
val client = createClientWithPinning()
assertThrows<SSLPeerUnverifiedException> {
client.newCall(
Request.Builder()
.url("https://evil.example.com/users")
.build()
).execute()
}
}
@Test
fun testMITMAttempt() = runTest {
val proxyClient = OkHttpClient.Builder()
.proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress("localhost", 8888)))
.certificatePinner(certificatePinner)
.build()
assertThrows<SSLHandshakeException> {
proxyClient.newCall(
Request.Builder()
.url("https://api.example.com/users")
.build()
).execute()
}
}
}
Public Key Pinning
class PublicKeyPinning : CertificatePinner {
private val pinnedPublicKeys = setOf(
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="
)
override fun check(hostname: String, peerCertificates: List<Certificate>) {
val publicKeyHashes = peerCertificates.map { cert ->
sha256(cert.publicKey.encoded).base64()
}
if (publicKeyHashes.none { it in pinnedPublicKeys }) {
throw SSLPeerUnverifiedException("Certificate pinning failure!")
}
}
}
Стратегия Ротации Pin’ов
Резервные Pin’ы
val certificatePinner = CertificatePinner.Builder()
.add("api.example.com",
"sha256/PRIMARY_PIN_HASH")
.add("api.example.com",
"sha256/BACKUP_PIN_HASH_1")
.add("api.example.com",
"sha256/BACKUP_PIN_HASH_2")
.build()
Удалённая Конфигурация Pin’ов
class DynamicPinningManager(
private val configService: ConfigService,
private val secureStorage: SecureStorage
) {
suspend fun updatePins() {
val newPins = configService.getCertificatePins()
if (validatePins(newPins)) {
secureStorage.savePins(newPins)
}
}
}
@Test
fun testPinRotation() = runTest {
val manager = DynamicPinningManager(mockConfigService, storage)
mockConfigService.respondWith(mapOf(
"api.example.com" to setOf("OLD_PIN", "NEW_PIN")
))
manager.updatePins()
val client = manager.createClient()
assertTrue(client.certificatePinner.pins.size == 2)
}
Тестирование Обнаружения MITM
Обнаружение MITM критически важно для мобильной безопасности. Изучите продвинутые техники тестирования безопасности в нашем руководстве по Основам Penetration Testing.
Обнаружение Bypass
class SecurityChecker {
fun detectRootAndBypassTools(): SecurityStatus {
val issues = mutableListOf<String>()
if (isDeviceRooted()) {
issues.add("Устройство с root")
}
if (isFridaRunning()) {
issues.add("Frida обнаружена")
}
return if (issues.isEmpty()) {
SecurityStatus.Secure
} else {
SecurityStatus.Compromised(issues)
}
}
}
Лучшие Практики
- Всегда используйте резервные pin’ы - Включайте 2-3 pin’а для ротации
- Pin к публичным ключам, не сертификатам - Более гибко для ротации
- Мониторьте истечение pin’ов - Настройте оповещения до истечения
- Тестируйте pinning в CI/CD - Обнаруживайте неправильную конфигурацию рано
- Реализуйте удалённые обновления - Для экстренной ротации
- Комбинируйте с другими мерами безопасности - Обнаружение root, валидация SSL
Certificate pinning работает лучше всего в сочетании с тестированием OAuth 2.0 и JWT для комплексной безопасности аутентификации. QA-инженерам, желающим расширить знания в области безопасности, рекомендуем Тестирование Безопасности для QA.
Заключение
Тестирование certificate pinning обеспечивает:
- Предотвращение MITM атак
- Правильную валидацию SSL/TLS
- Graceful ротацию pin’ов
- Обнаружение попыток bypass
Правильно реализованный и протестированный certificate pinning значительно улучшает безопасность мобильных приложений.