Arquitectura de firma integrada en el dispositivo

A partir de Android 12, el módulo de Android Runtime (ART) es un módulo de línea principal. Es posible que la actualización del módulo requiera que se recompilen los artefactos de compilación por adelantado (AOT) de los archivos JAR de bootclasspath y del servidor del sistema. Como estos artefactos son sensibles a la seguridad, Android 12 emplea una función llamada firma en el dispositivo para evitar que se manipulen. En esta página, se abarca la arquitectura de firma en el dispositivo y sus interacciones con otras funciones de seguridad de Android.

Diseño de alto nivel

La firma en el dispositivo tiene dos componentes principales:

  • odrefresh es parte del módulo de Mainline de ART. Es responsable de generar los artefactos del tiempo de ejecución. Verifica los artefactos existentes con la versión instalada del módulo de ART, los archivos JAR de bootclasspath y los archivos JAR del servidor del sistema para determinar si están actualizados o si deben regenerarse. Si es necesario regenerarlos, odrefresh los genera y almacena.

  • odsign es un archivo binario que forma parte de la plataforma de Android. Se ejecuta durante el inicio temprano, inmediatamente después de que se activa la partición /data. Su principal responsabilidad es invocar odrefresh para verificar si se deben generar o actualizar artefactos. Para cualquier artefacto nuevo o actualizado que genere odrefresh, odsign calcula una función hash. El resultado de este cálculo de hash se denomina resumen del archivo. En el caso de los artefactos que ya existen, odsign verifica que los resúmenes de los artefactos existentes coincidan con los resúmenes que odsign había calculado anteriormente. Esto garantiza que los artefactos no se hayan alterado.

En condiciones de error, como cuando el resumen de un archivo no coincide, odrefresh y odsign descartan todos los artefactos existentes en /data y tratan de volver a generarlos. Si eso falla, el sistema vuelve al modo JIT.

odrefresh y odsign están protegidos por dm-verity y forman parte de la cadena de inicio verificado de Android.

Cálculo de resúmenes de archivos con fs-verity

fs-verity es una función del kernel de Linux que realiza la verificación de datos de archivos basada en el árbol de Merkle. Habilitar fs-verity en un archivo hace que el sistema de archivos cree un árbol de Merkle sobre los datos del archivo con hashes SHA-256, lo almacene en una ubicación oculta junto con el archivo y marque el archivo como de solo lectura. fs-verity verifica automáticamente los datos del archivo con el árbol de Merkle a pedido a medida que se leen. fs-verity hace que el hash raíz del árbol de Merkle esté disponible como un valor llamado resumen del archivo fs-verity, y fs-verity garantiza que los datos que se leen del archivo sean coherentes con este resumen del archivo.

odsign usa fs-verity para mejorar el rendimiento del inicio optimizando la autenticación criptográfica de los artefactos compilados en el dispositivo durante el inicio. Cuando se genera un artefacto, odsign habilita fs-verity en él. Cuando odsign verifica un artefacto, verifica el resumen del archivo fs-verity en lugar del hash completo del archivo. Esto elimina la necesidad de leer y generar un hash de los datos completos del artefacto durante el inicio. En cambio, fs-verity genera un hash de los datos del artefacto a pedido a medida que se usan, bloque por bloque.

En los dispositivos cuyo kernel no admite fs-verity, odsign recurre a calcular resúmenes de archivos en el espacio del usuario. odsign usa el mismo algoritmo de hash basado en el árbol de Merkle que fs-verity, por lo que los resúmenes son los mismos en ambos casos. fs-verity es obligatorio en todos los dispositivos que se lanzaron con Android 11 y versiones posteriores.

Almacenamiento de resúmenes de archivos

odsign almacena los resúmenes de los archivos de los artefactos en un archivo separado llamado odsign.info. Para asegurarnos de que no se manipule odsign.info, este se firma con una clave de firma que tiene propiedades de seguridad importantes.odsign.info En particular, la clave solo se puede generar y usar durante el inicio anticipado, momento en el que solo se ejecuta código de confianza. Consulta Claves de firma de confianza para obtener más detalles.

Verificación de resúmenes de archivos

En cada inicio, si odrefresh determina que los artefactos existentes están actualizados, odsign garantiza que los archivos no se hayan manipulado desde que se generaron. odsign verifica los resúmenes de los archivos para hacerlo. Primero, verifica la firma de odsign.info. Si la firma es válida, odsign verifica que el resumen de cada archivo coincida con el resumen correspondiente en odsign.info.

Claves de firma de confianza

Android 12 introduce una nueva función de Keystore llamada claves de etapa de inicio que aborda los siguientes problemas de seguridad:

  • ¿Qué impide que un atacante use nuestra clave de firma para firmar su propia versión de odsign.info?
  • ¿Qué impide que un atacante genere su propia clave de firma y la use para firmar su propia versión de odsign.info?

Las claves de etapas de inicio dividen el ciclo de inicio de Android en niveles y vinculan de forma criptográfica la creación y el uso de una clave a un nivel específico. odsign crea su clave de firma en un nivel inicial, cuando solo se ejecuta código de confianza, protegido a través de dm-verity.

Los niveles de la etapa de arranque se numeran del 0 al número mágico 1000000000. Durante el proceso de inicio de Android, puedes aumentar el nivel de inicio configurando una propiedad del sistema desde init.rc. Por ejemplo, el siguiente código establece el nivel de inicio en 10:

setprop keystore.boot_level 10

Los clientes de Keystore pueden crear claves vinculadas a un determinado nivel de inicio. Por ejemplo, si creas una clave para el nivel de inicio 10, esa clave solo se puede usar cuando el dispositivo está en el nivel de inicio 10.

odsign usa el nivel de arranque 30, y la clave de firma que crea está vinculada a ese nivel de arranque. Antes de usar una clave para firmar artefactos, odsign verifica que la clave esté vinculada al nivel de inicio 30.

Esto evita los dos ataques que se describieron anteriormente en esta sección:

  • Los atacantes no pueden usar la clave generada, ya que, cuando tienen la oportunidad de ejecutar código malicioso, el nivel de inicio ya aumentó más allá de 30 y Keystore rechaza las operaciones que usan la clave.
  • Los atacantes no pueden crear una clave nueva, ya que, cuando tienen la oportunidad de ejecutar código malicioso, el nivel de inicio ya superó el 30 y Keystore se niega a crear una clave nueva con ese nivel de inicio. Si un atacante crea una clave nueva que no está vinculada al nivel de arranque 30, odsign la rechaza.

El almacén de claves garantiza que el nivel de inicio se aplique correctamente. En las siguientes secciones, se explica con más detalle cómo se realiza este proceso para las diferentes versiones de KeyMint (anteriormente Keymaster).

Implementación de Keymaster 4.0

Las diferentes versiones de Keymaster controlan la implementación de las claves de la etapa de inicio de manera diferente. En los dispositivos con un TEE/StrongBox de Keymaster 4.0, Keymaster controla la implementación de la siguiente manera:

  1. En el primer arranque, Keystore crea una clave simétrica K0 con la etiqueta MAX_USES_PER_BOOT establecida en 1. Esto significa que la clave solo se puede usar una vez por inicio.
  2. Durante el inicio, si se incrementa el nivel de inicio, se puede generar una clave nueva para ese nivel de inicio a partir de K0 con una función HKDF: Ki+i=HKDF(Ki, "some_fixed_string"). Por ejemplo, si pasas del nivel de arranque 0 al nivel de arranque 10, se invoca HKDF 10 veces para derivar K10 de K0.
  3. Cuando cambia el nivel de arranque, la clave del nivel de arranque anterior se borra de la memoria y las claves asociadas con los niveles de arranque anteriores ya no están disponibles.

    La clave K0 es una clave MAX_USES_PER_BOOT=1. Esto significa que también es imposible usar esa clave más adelante durante el inicio, ya que siempre se produce al menos una transición de nivel de inicio (al nivel de inicio final).

Cuando un cliente de Keystore, como odsign, solicita que se cree una clave en el nivel de arranque i, su BLOB se encripta con la clave Ki. Como Ki no está disponible después del nivel de arranque i, esta clave no se puede crear ni descifrar en etapas de arranque posteriores.

Implementación de Keymaster 4.1 y KeyMint 1.0

Las implementaciones de Keymaster 4.1 y KeyMint 1.0 son, en gran medida, las mismas que la implementación de Keymaster 4.0. La principal diferencia es que K0 no es una clave de MAX_USES_PER_BOOT, sino una clave de EARLY_BOOT_ONLY, que se introdujo en Keymaster 4.1. Una clave de EARLY_BOOT_ONLY solo se puede usar durante las primeras fases del arranque, cuando no se ejecuta código no confiable. Esto proporciona un nivel adicional de protección: en la implementación de Keymaster 4.0, un atacante que compromete el sistema de archivos y SELinux puede modificar la base de datos de Keystore para crear su propia clave MAX_USES_PER_BOOT=1 y firmar artefactos con ella. Este tipo de ataque es imposible con las implementaciones de Keymaster 4.1 y KeyMint 1.0, ya que las claves EARLY_BOOT_ONLY solo se pueden crear durante el arranque inicial.

Componente público de las claves de firma de confianza

odsign recupera el componente de clave pública de la clave de firma del almacén de claves. Sin embargo, Keystore no recupera esa clave pública del TEE/SE que contiene la clave privada correspondiente. En cambio, recupera la clave pública de su propia base de datos en el disco. Esto significa que un atacante que comprometa el sistema de archivos podría modificar la base de datos de Keystore para que contenga una clave pública que forme parte de un par de claves públicas y privadas bajo su control.

Para evitar este ataque, odsign crea una clave HMAC adicional con el mismo nivel de inicio que la clave de firma. Luego, cuando se crea la clave de firma, odsign usa esta clave HMAC para crear una firma de la clave pública y la almacena en el disco. En los inicios posteriores, cuando se recupera la clave pública de la clave de firma, se usa la clave HMAC para verificar que la firma en el disco coincida con la firma de la clave pública recuperada. Si coinciden, la clave pública es confiable, ya que la clave HMAC solo se puede usar en niveles de inicio tempranos y, por lo tanto, no puede haber sido creada por un atacante.