Una aplicación descarga la programación de un museo sin problemas, pero empieza a fallar después de renovar el certificado del servidor. En el ordenador, la web sigue abriendo. En algunos iPhone, también. La tentación es buscar una excepción que permita conectar cuanto antes, aunque el primer paso debería ser entender qué ha dejado de cuadrar.
HTTPS protege los datos durante el transporte y permite autenticar al servidor. Si eliminamos esa segunda comprobación, una conexión cifrada puede terminar enviando información a quien está suplantando nuestro servicio.
Lo que URLSession comprueba por nosotros
En una conexión HTTPS habitual, URLSession utiliza la evaluación de confianza del sistema. Apple recomienda mantener ese comportamiento salvo que exista una necesidad concreta de personalizarlo.
El sistema comprueba las firmas y la vigencia de los certificados, su uso para autenticar servidores y la existencia de una ruta hasta una autoridad de confianza. También verifica que el certificado corresponda al nombre del servidor solicitado.
Para este último punto, importa la extensión Subject Alternative Name, conocida como SAN. Desde iOS 13, poner el dominio únicamente en Common Name no basta. Para los certificados públicos, además, se aplican los requisitos de transparencia de certificados de Apple cuando corresponda.
App Transport Security, o ATS, añade requisitos de seguridad a las conexiones, como versiones y características criptográficas de TLS. Relajar la condiciones de ATS no repara un certificado caducado ni una cadena incompleta.
Conservar el error también forma parte del diagnóstico
La capa de red debería distinguir un fallo de confianza de una respuesta HTTP incorrecta. En nuestra aplicación del museo, podemos descargar la agenda conservando la validación del sistema:
import Foundation
struct Exhibition: Decodable {
let title: String
}
enum AgendaError: Error {
case invalidHTTPResponse
}
func loadExhibitions(from url: URL) async throws -> [Exhibition] {
do {
let (data, response) = try await URLSession.shared.data(from: url)
guard let http = response as? HTTPURLResponse,
(200..<300).contains(http.statusCode) else {
throw AgendaError.invalidHTTPResponse
}
return try JSONDecoder().decode([Exhibition].self, from: data)
} catch let error as URLError {
switch error.code {
case .serverCertificateUntrusted,
.serverCertificateHasBadDate,
.serverCertificateNotYetValid,
.serverCertificateHasUnknownRoot:
print("Fallo de confianza TLS: \(error.code.rawValue)")
default:
break
}
throw error
}
}
El ejemplo registra algunos errores relacionados con certificados y vuelve a propagarlos. Así, la interfaz puede comunicar el fallo sin presentar una agenda vacía como si fuera una respuesta válida. El código de error orienta la investigación, pero puede ser necesario revisar el error subyacente para identificar la causa exacta.
Si ya tenemos un delegado que recibe desafíos de autenticación, .performDefaultHandling conserva el tratamiento del sistema. Aceptar indiscriminadamente el objeto serverTrust mediante .useCredential puede saltarse comprobaciones necesarias.
El problema puede estar más arriba en la cadena
El certificado del servidor suele estar firmado por una autoridad intermedia, cuya confianza depende de otra autoridad hasta alcanzar una raíz reconocida. El servidor normalmente entrega su certificado y los intermedios; la raíz suele proceder del almacén de confianza del dispositivo.
Por eso, comprobar únicamente la fecha del certificado del dominio resulta insuficiente. Un certificado intermedio ausente o caducado también puede impedir la conexión. Apple explica estos fallos en su guía de evaluación de confianza HTTPS.
Además, una autoridad puede tener certificados con firmas de otras autoridades para ofrecer rutas alternativas. El nombre del emisor ayuda a seguirlas, pero una identificación precisa requiere comparar certificados, por ejemplo mediante su huella SHA-256.
Inspeccionar con OpenSSL y confirmar en iOS
Podemos examinar un servidor de pruebas con OpenSSL:
openssl s_client \
-connect agenda.example.org:443 \
-servername agenda.example.org \
-showcerts \
-verify_hostname agenda.example.org \
-verify_return_error
Sustituye el dominio por el del servicio real. -servername envía SNI para seleccionar el servidor virtual; -verify_hostname comprueba el nombre. Son funciones distintas. -verify_return_error interrumpe la conexión si falla la verificación.
La documentación de OpenSSL aclara que -showcerts muestra los certificados enviados por el servidor, sin convertir esa lista en una cadena validada.
Aunque aparezca Verify return code: 0 (ok), el resultado corresponde a la configuración de confianza de esa instalación de OpenSSL. Hay que confirmar el comportamiento con URLSession en las versiones de iOS compatibles, teniendo en cuenta certificados adicionales instalados mediante perfiles.
El pinning exige un plan de mantenimiento
El pinning impone una restricción adicional: además de superar la validación normal, el servidor debe presentar una identidad conocida por la aplicación.
Apple permite configurarlo mediante NSPinnedDomains en Info.plist, usando huellas SHA-256 de la estructura Subject Public Key Info de las claves públicas. Esa huella no equivale a calcular el hash del certificado completo.
Apple recomienda reservarlo para necesidades concretas. Una renovación que cambie las claves puede dejar sin conexión a aplicaciones ya instaladas. Antes de adoptarlo, conviene preparar claves alternativas, probar la rotación y definir cómo recuperar las versiones afectadas.
Una incidencia TLS se resuelve identificando qué comprobación falla y corrigiendo su causa. Mantener la evaluación del sistema, revisar la cadena completa y coordinar las renovaciones con el servidor permite conservar tanto la seguridad como la continuidad del servicio.