wandres.dev
SEGURIDAD Y PRIVACIDAD · permisos y datos

Seguridad de red: transporte, anclaje de certificados y secretos que no caben en el binario

El transporte seguro viene activado por defecto y las excepciones se justifican ante la revisión, así que el trabajo real está en otro sitio: decidir si anclar certificados aporta seguridad o solo riesgo operativo, y aceptar que en un cliente distribuido a millones de dispositivos no existe ningún lugar donde esconder un secreto. Esta lección analiza las tres capas —política de transporte, validación de confianza y custodia de credenciales— y propone la única arquitectura que sobrevive al hecho de que el atacante controla el dispositivo.

⏱ 18 min

Toda la seguridad de red de una app cliente descansa sobre una premisa incómoda que conviene aceptar en la primera línea en lugar de descubrirla tras un incidente: el atacante puede tener el dispositivo. Puede desactivar la comprobación de firmas, adjuntar un depurador al proceso, volcar la memoria, extraer el binario descifrado, sustituir tu delegado de sesión y colocar su propia autoridad certificadora en el almacén de confianza. Bajo esa premisa, ninguna medida ejecutada dentro del cliente es una garantía frente a su propio usuario; solo es un aumento del coste. La consecuencia no es el fatalismo, es un criterio de reparto: el cliente se encarga de proteger al usuario honesto frente a una red hostil, y el servidor se encarga de todo lo demás, porque es el único punto de la arquitectura que el adversario no controla.

🎯 Al terminar esta lección sabrás
  • Configurar la política de transporte del sistema y justificar cada excepción con criterio técnico.
  • Decidir con argumentos si anclar certificados y, en caso afirmativo, hacerlo de forma reversible.
  • Reconocer por qué ningún secreto sobrevive dentro de un binario distribuido públicamente.
  • Sustituir credenciales incrustadas por credenciales efímeras y atestación de integridad del cliente.

La política de transporte y sus excepciones

La seguridad del transporte de aplicaciones, conocida por sus siglas ATS, es una política que el sistema aplica a las conexiones que realizan las APIs de red de alto nivel, y su valor por defecto es exigente: transporte cifrado moderno, versiones antiguas del protocolo rechazadas, conjuntos de cifrado con secreto hacia adelante, certificados con algoritmos de firma robustos y claves de longitud suficiente. No es una comprobación que tú escribes: es una comprobación que ocurre antes de que tu código vea nada, y su fallo aparece como un error de conexión que muchos equipos diagnostican erróneamente como un problema de servidor.

Las excepciones se declaran en el Info.plist bajo la clave NSAppTransportSecurity, y aquí conviene distinguir las que denotan un problema de las que denotan una realidad. Permitir cargas arbitrarias de forma global es una bandera roja: desactiva la política entera, incluidas las conexiones de las que ni siquiera eres consciente, y la revisión pedirá una justificación que rara vez existe. Declarar excepciones por dominio, en cambio, es una decisión acotada y auditable: se nombra el dominio concreto, se rebaja el requisito mínimo imprescindible y el resto de la app sigue protegido. Existen además claves específicas para la red local y para el contenido cargado dentro de vistas web, precisamente porque esos dos casos tienen realidades distintas del tráfico principal de la app.

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSExceptionDomains</key>
  <dict>
    <key>legado.ejemplo.com</key>
    <dict>
      <key>NSExceptionMinimumTLSVersion</key><string>TLSv1.2</string>
      <key>NSIncludesSubdomains</key><false/>
    </dict>
  </dict>
</dict>

Hay dos detalles operativos que ahorran horas de depuración. El primero es que descubrir servicios en la red local exige, además de la política de transporte, la cadena de uso correspondiente y la declaración explícita de los servicios que se buscan; sin ello, el descubrimiento devuelve simplemente una lista vacía, sin error, y el equipo concluye que la red está mal. El segundo es que las APIs de bajo nivel del marco de red no pasan por esta política, de modo que una implementación propia sobre sockets no hereda ninguna de estas garantías y debe reproducirlas a mano, incluida la validación completa de la cadena de confianza. Bajar de nivel no es solo perder comodidad: es asumir la responsabilidad de todo lo que la capa superior hacía por ti.

⚠️
La excepción global se paga dos veces

Permitir cargas arbitrarias para toda la app no solo abre el dominio problemático: abre cualquier conexión que hagan tus dependencias, incluidas las que no auditaste. Y como la clave vive en el manifiesto compilado, cualquiera que descargue tu app puede leerla. Es una declaración pública de que tu tráfico admite degradación.

Anclar certificados: cuándo y, sobre todo, cómo salir

El anclaje consiste en no aceptar cualquier certificado válido para tu dominio sino solo aquel cuya clave pública, o la de alguno de sus emisores, coincide con un valor que tú fijaste de antemano. Protege de un escenario concreto y solo de ese: una autoridad certificadora legítima pero comprometida, coaccionada o engañada que emite un certificado válido para tu dominio a favor de un tercero. Es un riesgo real pero mucho menos frecuente de lo que la popularidad de la técnica sugiere, y además ya está parcialmente mitigado por la transparencia de certificados, que obliga a que toda emisión quede registrada en anotaciones públicas verificables donde una emisión indebida resulta detectable.

Frente a ese beneficio acotado hay un coste que se materializa con una probabilidad mucho más alta: el anclaje convierte cualquier rotación de certificado en un evento capaz de dejar inutilizable la app instalada. Si el valor anclado deja de coincidir —porque el proveedor de red de distribución cambió la cadena, porque el equipo de infraestructura renovó antes de tiempo, porque un balanceador nuevo presenta otro emisor—, todas las conexiones fallan a la vez, en todos los dispositivos, y la corrección exige publicar una versión nueva y esperar a que la revisión la apruebe y los usuarios la instalen. Se ha inmovilizado un producto entero por un problema de operaciones. Por eso la pregunta correcta no es si anclar, sino si tienes el proceso organizativo capaz de sostenerlo durante años.

📌

Ancla el emisor

Fijar la clave pública de la autoridad o del intermediario, y no la de la hoja, permite rotar el certificado del servidor sin publicar una versión nueva de la app.

🔁

Ancla de reserva

Declara siempre al menos dos valores: el vigente y el de la cadena a la que migrarás. Sin reserva, cualquier rotación imprevista es una interrupción total.

Caducidad y desactivación

Pon fecha de expiración al anclaje y un interruptor remoto para desactivarlo. Un anclaje sin salida es una bomba con temporizador escrita en tu propio binario.

La forma moderna de hacerlo en la plataforma es declarativa: se especifican en el manifiesto los dominios anclados y los resúmenes criptográficos de las claves públicas aceptadas, y el sistema aplica la comprobación dentro de su propia evaluación de confianza. Esa vía es netamente preferible a implementar el delegado de autenticación a mano, porque el camino manual concentra el error más grave y más común de toda la seguridad de red en apps: el desarrollador que, para hacer funcionar un entorno de pruebas, acepta incondicionalmente la confianza recibida y deja esa línea en producción. Ese fragmento no debilita la validación, la elimina por completo, y convierte la conexión en texto claro frente a cualquier intermediario.

// El antipatron que aparece en produccion mas veces de lo tolerable.
func urlSession(_ s: URLSession, didReceive reto: URLAuthenticationChallenge) async
    -> (URLSession.AuthChallengeDisposition, URLCredential?) {
    let confianza = reto.protectionSpace.serverTrust!
    return (.useCredential, URLCredential(trust: confianza))   // acepta cualquier cosa
}
flowchart TD
a[Peticion de red] --> b[Politica de transporte del sistema]
b -->|falla| x[Conexion rechazada antes de tu codigo]
b --> c[Evaluacion de confianza y anclaje declarativo]
c -->|no coincide| x
c --> d[Sesion establecida]
d --> e[Credencial efimera del backend]
e --> f[Atestacion de integridad del cliente]
f --> g[Autorizacion en el servidor]

No hay secretos en el binario

Queda la capa donde se cometen los errores más caros, y su enunciado es tajante: un binario distribuido a millones de dispositivos no es un lugar donde guardar un secreto, sin excepciones y sin matices. El artefacto de la tienda puede obtenerse descifrado desde un dispositivo con permisos elevados; a partir de ahí, una simple extracción de cadenas revela las claves literales, y un análisis algo más paciente localiza las que se generan en tiempo de ejecución observando la memoria del proceso. La ofuscación, el troceado de la cadena, el cifrado con una clave que también está dentro y el cálculo diferido no cambian la conclusión: solo elevan el tiempo del atacante de minutos a horas, y solo para el primero, porque el segundo se descarga el escrito que publicó el primero.

De ahí se sigue una distinción que ordena el diseño de cualquier integración. Existen credenciales que identifican a la aplicación como pieza pública, y esas pueden viajar en el cliente porque su exposición no otorga privilegios: un identificador de cliente, una clave pública, un identificador de proyecto. Y existen credenciales que otorgan capacidad —claves de servicios de terceros con coste por uso, secretos de cliente, credenciales de bases de datos, claves de firma—, y ninguna de ellas puede estar en el cliente bajo ninguna circunstancia. El patrón correcto es el intermediario: la app habla con tu backend, tu backend guarda la credencial con capacidad y habla con el tercero, y ahí ejerces además el control de gasto, el límite de frecuencia y la auditoría que el cliente jamás podrá aplicarse a sí mismo. Para los flujos de autorización delegada, el equivalente es el intercambio con verificador de código, diseñado precisamente para clientes públicos incapaces de custodiar un secreto.

Falta responder a la objeción evidente: si cualquiera puede extraer las peticiones, qué impide llamar directamente a ese backend imitando la app. La respuesta de la plataforma es la atestación de integridad, un servicio que produce una prueba criptográfica, respaldada por el hardware del dispositivo, de que la petición procede de una instancia genuina y sin modificar de tu app, en un dispositivo genuino. El servidor verifica esa prueba con los servicios de Apple y solo entonces atiende la solicitud. No es infalible ni pretende serlo, pero desplaza el problema al lugar correcto: el servidor decide si confía, con evidencia externa al cliente, en vez de confiar por defecto en cualquiera que sepa formar una petición. Combinado con credenciales de corta vida emitidas por tu backend y renovadas con frecuencia, el resultado es una arquitectura donde robar el binario no aporta absolutamente nada.

💡
Trata cualquier clave incrustada como ya filtrada

Si una clave estuvo alguna vez en una versión publicada, está comprometida aunque nadie lo haya notado: existe en los archivos de todas las versiones que tus usuarios conservan. La respuesta no es retirarla en la siguiente actualización, es rotarla en el servicio de origen y revocar la anterior. Todo lo demás es aplazar el incidente.

El cliente no es una frontera de confianza: es una interfaz de usuario con conexión a internet

El modelo mental que resuelve de golpe las tres capas de esta lección es aceptar que tu app no es una parte de tu sistema que puedas defender, sino la parte de tu sistema que está en manos del adversario, y que sin embargo debe seguir siendo útil para el usuario legítimo. Esa doble condición explica por qué cada medida vale exactamente lo que vale y ni un gramo más. La política de transporte protege de verdad, porque su adversario es la red y no el usuario: nadie en una cafetería puede modificar la política del sistema del dispositivo ajeno. El anclaje protege de un adversario intermedio, no del dueño del teléfono, y por eso su valor debe compararse honestamente con su riesgo operativo en lugar de asumirse como buena práctica universal. Y los secretos incrustados no protegen de nada, porque su adversario es precisamente quien ejecuta el código. Ordenadas así, las decisiones dejan de ser cuestión de gusto: se anclan las medidas cuyo adversario está fuera del dispositivo, se trasladan al servidor todas aquellas cuyo adversario está dentro, y en la frontera se coloca lo único que un cliente sí puede aportar de forma no falsificable, una prueba respaldada por hardware de que es quien dice ser. El objetivo final no es un cliente inexpugnable, que no existe, sino un sistema en el que comprometer completamente un cliente no comprometa nada más que a ese cliente. Cuando esa propiedad se cumple, el volcado del binario deja de ser una crisis y se convierte en un dato sin consecuencias.

📝
Lo esencial

La política de transporte viene activada y exigente: usa excepciones por dominio y nunca globales, y recuerda que la red local pide su propia declaración y que las APIs de bajo nivel no heredan nada. El anclaje solo cubre el caso de una autoridad comprometida y a cambio arriesga una interrupción total: si lo adoptas, ancla el emisor, incluye reserva, caducidad e interruptor remoto, y hazlo de forma declarativa. Ningún secreto con capacidad puede vivir en el binario: usa un intermediario en tu backend, credenciales efímeras y atestación de integridad del dispositivo.

⚔️ Del binario a la frontera correcta
  1. Extrae las cadenas del artefacto compilado de tu app y busca patrones de clave y credencial; toda coincidencia va a una lista de rotación con responsable y fecha.
  2. Revisa el manifiesto en busca de excepciones de transporte, elimina cualquier permiso global y sustitúyelo por excepciones por dominio con la versión mínima más alta que funcione.
  3. Audita todos los delegados de autenticación de tu base de código y de tus dependencias buscando aceptaciones incondicionales de confianza; documenta cada uno antes de borrarlo.
  4. Si anclas, simula una rotación imprevista en un entorno de pruebas y cronometra cuánto tardarías en recuperar el servicio; si la respuesta supera un día, diseña el interruptor remoto antes de seguir.
  5. Elige la integración de terceros más sensible que hoy llama desde el cliente, muévela detrás de tu backend y protege ese punto con atestación de integridad y credenciales de corta vida.