El certificate pinning fortalece la seguridad SSL/TLS validando que el certificado del servidor coincida con un certificado conocido y confiable. Esto previene ataques man-in-the-middle (MITM) incluso cuando el trust store del dispositivo está comprometido.
Para una visión completa de los fundamentos de seguridad móvil, consulta nuestra guía de Testing de Seguridad Móvil.
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()
Generando Pins de Certificados
# Extraer certificado del servidor
openssl s_client -connect api.example.com:443 -servername api.example.com < /dev/null \
| openssl x509 -outform DER > cert.der
# Generar hash SHA-256
openssl x509 -in cert.der -inform DER -pubkey -noout \
| openssl pkey -pubin -outform DER \
| openssl dgst -sha256 -binary \
| base64
Testing 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!")
}
}
}
Estrategia de Rotación de Pins
Pins de Respaldo
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()
Configuración Remota de Pins
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)
}
Testing de Detección MITM
La detección MITM es crítica para la seguridad móvil. Aprende técnicas avanzadas de testing de seguridad en nuestra guía de Fundamentos de Penetration Testing.
Detección de Bypass
class SecurityChecker {
fun detectRootAndBypassTools(): SecurityStatus {
val issues = mutableListOf<String>()
if (isDeviceRooted()) {
issues.add("Dispositivo rooteado")
}
if (isFridaRunning()) {
issues.add("Frida detectado")
}
return if (issues.isEmpty()) {
SecurityStatus.Secure
} else {
SecurityStatus.Compromised(issues)
}
}
}
Mejores Prácticas
- Siempre usa pins de respaldo - Incluye 2-3 pins para rotación
- Pin a claves públicas, no certificados - Más flexible para rotación
- Monitorea expiración de pins - Configura alertas antes de que expiren
- Prueba pinning en CI/CD - Detecta mala configuración temprano
- Implementa actualizaciones remotas - Para rotación de emergencia
- Combina con otras medidas de seguridad - Detección root, validación SSL
El certificate pinning funciona mejor cuando se combina con testing de OAuth 2.0 y JWT para seguridad completa de autenticación. Para ingenieros QA que buscan expandir conocimientos de seguridad, consulta Testing de Seguridad para QA.
Conclusión
El testing de certificate pinning asegura:
- Prevención de ataques MITM
- Validación apropiada SSL/TLS
- Rotación graceful de pins
- Detección de intentos de bypass
El certificate pinning correctamente implementado y probado mejora significativamente la seguridad de apps móviles.