Certificate pinning усиливает безопасность SSL/TLS, валидируя, что сертификат сервера совпадает с известным, доверенным сертификатом. Это предотвращает man-in-the-middle (MITM) атаки даже когда trust store устройства скомпрометирован.
Для комплексного обзора основ мобильной безопасности смотрите наше руководство по Тестированию Мобильной Безопасности. Тестирование certificate pinning тесно связано с общими практиками тестирования безопасности API, где SSL/TLS валидация играет ключевую роль. Также рекомендуем изучить документирование тестов безопасности для правильного оформления результатов проверок pinning.
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 значительно улучшает безопасность мобильных приложений.
Смотрите также
- Тестирование Мобильной Безопасности - Комплексное руководство по безопасности мобильных приложений
- Тестирование Безопасности API - SSL/TLS валидация и защита API endpoints
- Документация Тестирования Безопасности - Оформление результатов тестирования pinning
- Техники Дизайна Тест-Кейсов - Проектирование тестов для различных сценариев pinning
- Непрерывное Тестирование в DevOps - Автоматизация проверок certificate pinning