Usando el Secure Enclave para mejorar la integridad de aplicaciones iOS

Usando el Secure Enclave para mejorar la integridad de aplicaciones iOS

Álvaro Sánchez Pinzón, iOS Developer

Álvaro Sánchez Pinzón

iOS Developer

23 de septiembre de 2024

¿Qué es el Secure Enclave?

Para definir qué es el Secure Enclave, vamos a hacer una analogía. Supongamos que tenemos una casa y dentro tenemos una caja fuerte supersegura y especial:

  • La casa representa tu dispositivo. Dentro de tu casa tienes muchas estancias donde guardas cosas (aplicaciones, fotos, mensajes, contactos…) y cualquier persona que tenga la llave de la casa puede entrar y tener acceso a todas estas cosas.

  • La caja fuerte es el Secure Enclave. La caja fuerte está dentro de la casa, pero necesitas un acceso especial para acceder a su interior, por tanto, está completamente aislada de tu casa. Supongamos que tienes ciertas pertenencias como joyas o documentos que consideras muy importantes y que no quieres que nadie sin permiso tenga acceso a ellas. Éstas estarán a salvo en la caja fuerte.

  • Las joyas o documentos que guardas en tu caja fuerte serían los datos biométricos (Face ID, Touch ID), claves criptográficas…

  • Para abrir la caja fuerte se tiene que realizar un proceso muy riguroso (mediante Face ID o Touch ID). Cada vez que intentas abrir la caja fuerte, la caja verifica muy meticulosamente que seas tú quien la está abriendo. Si el proceso de apertura es satisfactorio, la caja permite acceder a tus objetos valiosos durante unos instantes, pero nunca permite que los saques fuera de la caja. Todo el proceso ocurre dentro de la caja fuerte y nadie sabe qué pasa dentro (encriptado y desencriptado, firmas…).

  • La caja fuerte es muy segura. Aunque alguien consiguiera entrar en tu casa, tendría acceso a todas tus cosas menos a las de la caja fuerte. Si se intentase forzar, la caja fuerte se bloquearía y no permitiría ningún acceso.

En resumen, el Secure Enclave es una caja fuerte dentro del dispositivo que asegura que solo tú tengas acceso a tus datos más valiosos (como tus datos biométricos) y ayuda a realizar ciertas operaciones criptográficas añadiendo más seguridad a las mismas.

Ahora, vamos a definir el Secure Enclave de una forma un poco más técnica:

Es un subsistema de seguridad que se integra en los SoC de la mayoría de los dispositivos de Apple.  En concreto está disponible para:

  • iPhone 5s o posterior

  • iPad Air o posterior

  • Ordenadores MacBook Pro con Touch Bar (2016 y 2017) con el chip T1 de Apple

  • Ordenadores Mac basados en Intel con el chip de seguridad T2 de Apple

  • Ordenadores Mac con chip de Apple

  • Apple TV HD o posterior

  • Apple Watch Series 1 o posterior

  • HomePod y HomePod mini

El Secure Enclave está aislado del procesador principal, lo que le da una capa adicional de seguridad, ya que si el procesador principal se ve comprometido, el Secure Enclave mantiene su integridad. Tiene su propia ROM de arranque con la que establece una raíz de confianza de hardware (mediante el UID y el GID) y un motor AES para operaciones criptográficas seguras. Así como un almacenamiento independiente del que usa el sistema operativo y aplicaciones del sistema.

Más información

SecureEnclaveesquema-.webp Esquema Secure Enclave.

Funcionalidades del Secure Enclave

El Secure Enclave tiene diferentes funcionalidades que permiten que el dispositivo sea más seguro:

  • Motor de protección de memoria. El Secure Enclave utiliza una zona dedicada de la memoria DRAM del dispositivo. Este motor de protección de memoria se encarga de encriptar y desencriptar la memoria utilizada por el Secure Enclave para que solo éste pueda usarla.

  • ROM de arranque. El Secure Enclave tiene una ROM de arranque dedicada, un código inmutable que establece la raíz de confianza de hardware para el Secure Enclave. Gracias al monitor de arranque la ROM de arranque del Secure Enclave comprueba el hash criptográfico y la firma de la imagen del sepOS (firmware del Secure Enclave), si es correcta, permite que se ejecute en el dispositivo. En el caso de que sea incorrecta, el Secure Enclave no se podrá usar en el dispositivo.

  • Generador de números aleatorios verdaderos. Todos sabemos lo complejo que es generar un número lo suficientemente aleatorio con software/hardware. El Secure Enclave incorpora un generador de números aleatorios que usa siempre que genera una clave criptográfica. Este generador se basa en varios osciladores en anillos postprocesados con CTR_DRBG.

  • Claves criptográficas raíz. El Secure Enclave incluye una clave criptográfica raíz de identificador único (UID), única para cada dispositivo. Esta clave es utilizada para proteger los secretos específicos del dispositivo.

  • Motores AES. Encargados de operaciones de criptografía simétrica.

  • Acelerador de claves públicas. Encargadas de operaciones de criptografía asimétrica.

  • Almacenamiento seguro no volátil. El Secure Enclave está equipado con un almacenamiento no volátil dedicado al que solo el Secure Enclave tiene acceso. Es utilizado para el almacenamiento de claves privadas, motores criptográficos, generador de números aleatorios…

  • Neural Engine seguro. Encargado de gestionar Face ID.

Como vemos, el Secure Enclave es un procesador que se encarga de realizar muchas operaciones críticas para la seguridad del dispositivo. En este artículo, vamos a centrarnos en la generación y gestión de claves públicas/privadas para garantizar la integridad del dispositivo.

Asegurando la integridad del dispositivo

Una de las formas disponibles que tenemos para asegurar que nuestra aplicación se está comunicando con el servidor que queremos evitando ataques MITM (Man in the middle) es mediante el SSL Pinning. De esta manera, comprobamos que estamos atacando a un servidor cuyo certificado es el que esperamos.

sslpinning1.webp Proceso de SSL Pinning

Supongamos ahora que estamos desarrollando una aplicación bancaria. Es posible que haya ciertas partes de la aplicación que no requieren comprobar que la instancia de la aplicación y el dispositivo en el que se está ejecutando es legítimo, como por ejemplo, consultar ofertas de financiación comunes para todo el público, tipos de cuenta… Pero habrá cierto punto en la aplicación en la que queramos que el usuario pueda acceder a sus detalles bancarios, movimientos, hacer transferencias… Hasta este punto, hemos asegurado que la aplicación se está comunicando con el servidor correcto mediante SSL Pinning. Pero, ¿qué pasa si nuestro servidor quiere asegurarse de que se está comunicando con un dispositivo seguro así como que la instancia de la aplicación es legítima? Para esto usaremos el App Attest Service.

AttestationAssertion.webp Proceso de Attestation/Assertion.

La solución que provee Apple para este problema es, haciendo uso del Secure Enclave, crear un par de claves público-privadas certificadas por Apple asegurando que las claves provienen de una instancia legítima de la aplicación. Posteriormente, podremos hacer uso de este par de claves certificadas por Apple para firmar futuras peticiones que hagamos a nuestro backend asegurando de que se está comunicando con una instancia legítima de la aplicación.

Y la pregunta que nos estamos haciendo todos es, ¿cómo Apple sabe si este par de claves se han generado desde el Secure Enclave? Tras varias pruebas, si intentamos verificar un par de claves que han sido generadas desde otra fuente que no sea el Secure Enclave, el servicio de App Attest de Apple nos devuelve un error. Esto es porque Apple incluye una clave privada dentro del Secure Enclave que está vinculada a la infraestructura PKI de Apple. De forma que cuando los servidores de Apple reciben el par de claves, verifican que las claves han sido generadas y firmadas con un certificado de confianza (firmado a su vez por la clave privada preconfigurada en el Secure Enclave) dentro de la PKI de Apple.

Para poder utilizar esta verificación de la integridad de la aplicación es necesario cumplir con lo siguientes requisitos:

  • La aplicación solo será compatible con dispositivos que tengan el Secure Enclave (iPhone 5s o superior).

  • Es necesario disponer de iOS 14 o superior.

  • Para usar el servicio de App Attest, es necesario tener un App ID registrado en el portal de desarrollador de Apple.

Comprobar la disponibilidad

Antes de utilizar el servicio de App Attest, debemos importar la librería DeviceCheck y comprobar que está disponible en el dispositivo actual. Para ello:

var isSupported: Bool { get }

Documentación

Generación de claves y uso de las mismas

El servicio de App Attest consiste en generar un par de claves público-privadas (del formato EC P-256) utilizando el Secure Enclave y validarlas contra Apple. A partir de este momento, todas las peticiones que hagamos al servidor pueden ir firmadas con esta clave para que el servidor tenga garantizada la integridad de la aplicación.

Es importante remarcar que las claves que se generan en el Secure Enclave nunca salen del mismo. Cuando se generan un par de claves, la clave privada se guarda en el Secure Enclave y no es posible extraerla del mismo. Por seguridad, ni la aplicación ni el sistema conocen la clave generada.

Para generar el par de claves basadas en hardware utilizaremos el método:


func generateKey(completionHandler: @escaping (String?, (any Error)?) -> Void)

Documentación

Cabe destacar que todos los métodos que vamos a utilizar están disponibles también de forma asíncrona mediante async/await.

Este método devuelve un identificador de clave  que utilizaremos posteriormente para hacer operaciones con las claves generadas o un error si ha habido algún problema. Es importante guardar este identificador de clave de forma persistente (ya sea en Keychain, o en el servidor), puesto que como se ha comentado anteriormente, no podemos recuperar las claves generadas en el Secure Enclave y la única forma que tenemos de operar con ellas es mediante este identificador.

Si en algún momento perdemos el identificador, tendremos que generar un nuevo par de claves y repetir todo el proceso de verificación de claves contra Apple.

Si nuestra aplicación permite operar con diferentes usuarios, es recomendable generar un par de claves para cada usuario. De esta forma, es más sencillo identificar posibles fraudes posteriores si las peticiones vienen firmadas con diferentes claves.

Certificar el par de claves como válidas

Una vez generadas el par de claves, deberemos verificar que se han generado desde una aplicación y dispositivo que no están comprometidos. Para ello, se certificará contra Apple este par de claves.

Es importante destacar que este es el único momento en el que los servidores de Apple intervienen en el App Attest. Una vez verificadas el par de claves contra Apple, las siguientes peticiones que hagamos al servidor irán firmadas de forma local mediante la clave privada que hemos verificado con Apple. Como todos los servicios, debemos intentar no saturar el servicio de attestKey para evitar errores inesperados. Para este caso, la documentación de Apple recomienda que las features que dependan de este servicio sean liberadas de forma progresiva. Los servicios tienen un límite de 100 peticiones por segundo y por Bundle Identifier, este límite puede variar en función de la saturación que experimenten los servicios de Apple.

Cuando vamos a crear un par de claves y verificarlas contra Apple, es recomendable que el servidor al que posteriormente vamos a enviar la clave pública para que verifique la legitimidad de nuestras peticiones nos provea de un challenge único que solo valga para esta verificación. De esta forma, evitaremos ataques de repetición y nos aseguraremos de que este par de claves solo es válido para esta transacción.

Para certificar las claves contra Apple, utilizaremos el método:

func attestKey(

     \_keyId: String,

     clientDataHash: Data,

     completionHandler: @escaping (Data?, (any Error)?) -> Void

)

Documentación

Los parámetros de entrada son:

  • KeyId: El identificador de la clave obtenido al generar el par de claves.

  • clientDataHash: El hash SHA256 del challenge emitido por el servidor.

Tras verificar las claves contra Apple, tendremos que enviar esta certificación a nuestro servidor con una serie de datos que utilizarán para verificar posteriores firmas que hagamos en las peticiones.

Si la ejecución del método es correcta, nos devolverá un attestationObject que es el objeto de tipo Data. Este objeto tiene toda la información que nuestro servidor necesita para comprobar que el par de claves se ha verificado correctamente así como la clave pública necesaria para verificar la firma de posteriores peticiones. En detalle, los campos que contiene son:

  • RP ID (32 bytes) - Un hash del App ID formado de la siguiente forma: concatenación del team identifier, un punto y el bundleIdentifier de la aplicación.

  • counter (4 bytes) - Valor que informa del número de veces que la aplicación ha usado la clave para firmar un assertion (firma de una request).

  • aaguid (16 bytes) - Constante que indica si la key pertenece al entorno de producción o de desarrollo.

  • credentialId (32 bytes) - Contiene el hash de la clave pública del par de claves generada en el Secure Enclave.

La forma más sencilla para enviar toda esta información al servidor es generando un base64 String de los datos recibidos por el servicio de attest y enviarlo al servidor. El procedimiento que tiene que seguir el servidor para gestionar estos attestations está detallado aquí.

Existen varios errores que puede devolver este método. Pero en particular, si devuelve un serverUnavailable, Apple recomienda volver a intentar el attest con el mismo par de claves por si ha habido un error de conexión. Si al reintentarlo sigue fallando, es recomendable generar un nuevo par de claves e intentarlo de nuevo.

Firmar peticiones

Una vez generado y certificado por Apple el par de claves, podemos firmar cualquier petición que vayamos a hacer al servidor para que este compruebe que la petición le está llegando desde una aplicación legítima.

No hay restricción con el número de assertions que se pueden hacer, de hecho, es una operación que se hace de forma local en el dispositivo. Por lo que no necesitamos volver a comunicarnos con los servidores de Apple. La decisión de qué peticiones firmar corresponde a cada proyecto. Es posible que solo sea necesario firmar peticiones con datos comprometidos, cuando se van a descargar contenidos de pago o para acceder a contenidos premium de la aplicación.

Procesodefirmadepeticiones.webp Proceso de firma de peticiones.

Para firmar una petición, como en pasos anteriores, es recomendable que nuestro servidor nos provea de un challenge para evitar ataques de repetición. Es algo intuitivo, pero no está de más recalcar que solo los datos que estén firmados por el assertion son en los que podemos confiar. Si añadimos cabeceras o algún dato en la request que no está firmado por el assertion el App Attest no estaría cubriendo esos datos.

En primer lugar se calcula el hash SHA256 de los datos que se quieren firmar. Posteriormente se crea un objeto Data con el hash calculado:

let challenge = <# Un string que se ha generado desde nuestro servidor>  
let request = \[ "action": "getClientDetails", "clientId": "12345", "challenge": challenge \]  
guard let clientData = try? JSONEncoder().encode(request) else { return }  
let clientDataHash = Data(SHA256.hash(data: clientData))

El proceso de firma consiste en pasar el clientDataHash (que es la request que vamos a hacer a nuestro servidor) junto con el identificador de clave al Secure Enclave para que este los firme con la clave privada y nos los devuelva firmados.

Para firmar estos datos, se utiliza el método:

func generateAssertion(

    \_ keyId: String, 

    clientDataHash: Data, 

    completionHandler: @escaping (Data?, (any Error)?) -> Void

)

Documentación

Los parámetros de entrada son:

  • keyId: Identificador del par de claves generadas por el Secure Enclave.

  • clientDataHash: Datos a firmar.

Si todo ha ido bien, nos devolverá un assertionObject donde se encuentra la firma de los datos que deberemos pasar junto con la request (en alguna cabecera por ejemplo) a nuestro servidor para que compruebe la validez de la misma.

El resumen de esta comprobación por parte del servidor es el siguiente:

  • El servidor recibe el assertionObject junto con la request en plano que el cliente está realizando.

  • El servidor comprueba que los datos del assertionObject están correctamente firmados por la clave privada asociada a la clave pública que ha obtenido tras haber verificado las claves contra Apple.

  • El servidor comprueba que los datos de la petición que están firmados en el assertionObject coinciden con la petición recibida en plano.

    • Si el contenido coincide, significa que esta petición ha sido realizada legítimamente desde el dispositivo y que no ha sido modificada en el transcurso del cliente al servidor.

    • Si el contenido no coincide, significa que los datos de la petición en plano han sido modificados desde que el cliente los envió. Por lo que el servidor deberá tomar medidas para rechazar la petición.

Conclusiones

En resumen, el servicio de App Attest es una herramienta que Apple nos proporciona para aprovechar la seguridad del Secure Enclave. En este artículo hemos visto una de sus utilidades, pero como hemos visto en el artículo, podemos usarlo para implementar otros mecanismos de seguridad como criptografía simétrica, generación de números aleatorios… Ningún proceso en ciberseguridad, y particularmente en aplicaciones móviles, es completamente infalible, la clave reside en añadir tantas capas de protección como sea posible para disuadir a los atacantes.

El servicio de App Attest, junto con otras recomendaciones de seguridad, como las enumeradas por OWASP, nos permite desarrollar aplicaciones seguras que brindan tranquilidad a los usuarios en un entorno donde cada vez más datos personales residen en nuestros dispositivos móviles. En SNGULAR, adoptamos estas y otras técnicas avanzadas para asegurar que las aplicaciones que desarrollamos cumplan con los más altos estándares de seguridad.

En artículos posteriores, introduciremos una forma de realizar esta misma verificación del cliente ante el servidor en dispositivos Android.

Bibliografía

Álvaro Sánchez Pinzón, iOS Developer

Álvaro Sánchez Pinzón

iOS Developer

¡Hola! Soy Álvaro Sánchez Pinzón, desarrollador iOS en SNGULAR. Soy Ingeniero en Sistemas Audiovisuales, y aunque desde 2019 me centro en el desarrollo de aplicaciones para el ecosistema de Apple, me encanta trastear con todas las tecnologías que están a mi alcance así como enseñar y aprender de los demás todo lo posible.


Nuestras últimas novedades

¿Te interesa saber cómo nos adaptamos constantemente a la nueva frontera digital?

Contract Testing con Pact - La guía definitiva
Contract Testing con Pact - La guía definitiva

Tech Insight

19 de diciembre de 2024

Contract Testing con Pact - La guía definitiva

Agilidad, complejidad y método empírico
Agilidad, complejidad y método empírico

Insight

18 de diciembre de 2024

Agilidad, complejidad y método empírico

Tecnologías rompedoras hoy, que redibujarán el mapa de la innovación en 2025
Tecnologías rompedoras hoy, que redibujarán el mapa de la innovación en 2025

Insight

10 de diciembre de 2024

Tecnologías rompedoras hoy, que redibujarán el mapa de la innovación en 2025

El ecosistema digital con el que Vítaly reinventa el cuidado de la salud
El ecosistema digital con el que Vítaly reinventa el cuidado de la salud

Insight

26 de noviembre de 2024

El ecosistema digital con el que Vítaly reinventa el cuidado de la salud