Vinculación de versión

En Keymaster 1, todas las claves de Keymaster estaban vinculadas de forma criptográfica a la raíz de confianza del dispositivo o a la clave de arranque verificado. En Keymaster 2 y 3, todas las claves también están vinculadas al sistema operativo y al nivel de parche de la imagen del sistema. Esto garantiza que un atacante que descubra una debilidad en una versión anterior del software del sistema o del TEE no pueda revertir un dispositivo a la versión vulnerable y usar las claves creadas con la versión más reciente. Además, cuando se usa una clave con una versión y un nivel de parche determinados en un dispositivo que se actualizó a una versión o un nivel de parche más recientes, la clave se actualiza antes de que se pueda usar, y se invalida la versión anterior de la clave. De esta manera, a medida que se actualiza el dispositivo, las claves avanzan junto con él, pero cualquier reversión del dispositivo a una versión anterior hace que las claves queden inutilizables.

Para admitir la estructura modular de Treble y romper la vinculación de system.img a boot.img, Keymaster 4 cambió el modelo de vinculación de versión de la clave para tener niveles de parche separados para cada partición. Esto permite que cada partición se actualice de forma independiente y, al mismo tiempo, se ofrece protección contra reversiones.

Para implementar esta vinculación de versión, la app de confianza (TA) de KeyMint necesita una forma de recibir de manera segura la versión actual del SO y los niveles de parches, y de garantizar que la información que recibe coincida con toda la información sobre el sistema en ejecución.

  • Los dispositivos con inicio verificado de Android (AVB) pueden colocar todos los niveles de parche y la versión del sistema en vbmeta, de modo que el bootloader pueda proporcionarlos a Keymaster. En el caso de las particiones encadenadas, la información de la versión de la partición se encuentra en el vbmeta encadenado. En general, la información de la versión debe estar en el vbmeta struct que contiene los datos de verificación (hash o árbol de hash) para una partición determinada.
  • En dispositivos sin AVB:
    • Las implementaciones de Verified Boot deben proporcionar un hash de los metadatos de la versión al bootloader, de modo que este pueda proporcionar el hash a Keymaster.
    • boot.img puede seguir almacenando el nivel de parche en el encabezado
    • system.img puede seguir almacenando el nivel de parche y la versión del SO en propiedades de solo lectura.
    • vendor.img almacena el nivel de parche en la propiedad de solo lectura ro.vendor.build.version.security_patch.
    • El cargador de arranque puede proporcionar un hash de todos los datos validados por el inicio verificado a Keymaster.
  • En Android 9, usa las siguientes etiquetas para proporcionar información de la versión de las siguientes particiones:
    • VENDOR_PATCH_LEVEL: partición vendor
    • BOOT_PATCH_LEVEL: partición boot
    • OS_PATCH_LEVEL y OS_VERSION: Partición system. (OS_VERSION se quita del encabezado boot.img).
  • Las implementaciones de Keymaster deben tratar todos los niveles de parche de forma independiente. Las claves se pueden usar si toda la información de la versión coincide con los valores asociados a una clave y IKeymaster::upgradeDevice() se actualiza a un nivel de parche superior si es necesario.

Cambios en el HAL

Para admitir la vinculación y la certificación de versiones, Android 7.1 agregó las etiquetas Tag::OS_VERSION y Tag::OS_PATCHLEVEL, y los métodos configure y upgradeKey. Las etiquetas de versión se agregan automáticamente a todas las claves recién generadas (o actualizadas) por las implementaciones de Keymaster 2+. Además, cualquier intento de usar una clave que no tenga una versión del SO o un nivel de parche que coincidan con la versión del SO o el nivel de parche del sistema actual, respectivamente, se rechaza con ErrorCode::KEY_REQUIRES_UPGRADE.

Tag::OS_VERSION es un valor de UINT que representa las partes principal, secundaria y subsecundaria de una versión del sistema Android como MMmmss, donde MM es la versión principal, mm es la versión secundaria y ss es la versión subsecundaria. Por ejemplo, 6.1.2 se representaría como 060102.

Tag::OS_PATCHLEVEL es un valor de UINT que representa el año y el mes de la última actualización del sistema como AAAAMM, donde AAAA es el año de cuatro dígitos y MM es el mes de dos dígitos. Por ejemplo, marzo de 2016 se representaría como 201603.

UpgradeKey

Para permitir que las claves se actualicen a la nueva versión del SO y al nivel de parche de la imagen del sistema, Android 7.1 agregó el método upgradeKey a la HAL:

Keymaster 3

    upgradeKey(vec keyBlobToUpgrade, vec upgradeParams)
        generates(ErrorCode error, vec upgradedKeyBlob);

Keymaster 2

keymaster_error_t (*upgrade_key)(const struct keymaster2_device* dev,
    const keymaster_key_blob_t* key_to_upgrade,
    const keymaster_key_param_set_t* upgrade_params,
    keymaster_key_blob_t* upgraded_key);
  • dev es la estructura del dispositivo.
  • keyBlobToUpgrade es la clave que se debe actualizar.
  • upgradeParams son los parámetros necesarios para actualizar la clave. Estos incluyen Tag::APPLICATION_ID y Tag::APPLICATION_DATA, que son necesarios para desencriptar el blob de la clave si se proporcionaron durante la generación.
  • upgradedKeyBlob es el parámetro de salida que se usa para devolver el nuevo BLOB de clave.

Si se llama a upgradeKey con un BLOB de clave que no se puede analizar o que no es válido, se devuelve ErrorCode::INVALID_KEY_BLOB. Si se llama con una clave cuyo nivel de parche es mayor que el valor actual del sistema, devuelve ErrorCode::INVALID_ARGUMENT. Si se llama con una clave cuya versión del SO es mayor que el valor actual del sistema y el valor del sistema no es cero, devuelve ErrorCode::INVALID_ARGUMENT. Se permiten las actualizaciones de la versión del SO de un valor distinto de cero a cero. En caso de errores de comunicación con el mundo seguro, devuelve un valor de error apropiado (por ejemplo, ErrorCode::SECURE_HW_ACCESS_DENIED, ErrorCode::SECURE_HW_BUSY). De lo contrario, devuelve ErrorCode::OK y un nuevo blob de clave en upgradedKeyBlob.

keyBlobToUpgrade sigue siendo válido después de la llamada a upgradeKey y, en teoría, podría volver a usarse si se revirtiera la versión del dispositivo. En la práctica, el almacén de claves generalmente llama a deleteKey en el blob keyBlobToUpgrade poco después de la llamada a upgradeKey. Si keyBlobToUpgrade tenía la etiqueta Tag::ROLLBACK_RESISTANT, upgradedKeyBlob también debería tenerla (y debería ser resistente a la reversión).

Configuración segura

Para implementar la vinculación de versiones, el TA de Keymaster necesita una forma de recibir de manera segura la versión actual del SO y el nivel de parche (información de la versión), y garantizar que la información que recibe coincida de forma precisa con la información sobre el sistema en ejecución.

Para admitir la entrega segura de información de versión al TA, se agregó un campo OS_VERSION al encabezado de la imagen de arranque. La secuencia de comandos de compilación de la imagen de arranque completa este campo automáticamente. Los OEM y los implementadores de TA de Keymaster deben trabajar juntos para modificar los bootloaders de los dispositivos y extraer la información de la versión de la imagen de arranque, y pasarla a la TA antes de que se inicie el sistema no seguro. Esto garantiza que los atacantes no puedan interferir en el aprovisionamiento de la información de la versión en la TA.

También es necesario asegurarse de que la imagen del sistema tenga la misma información de versión que la imagen de arranque. Para ello, se agregó el método configure a la HAL de Keymaster:

keymaster_error_t (*configure)(const struct keymaster2_device* dev,
  const keymaster_key_param_set_t* params);

El argumento params contiene Tag::OS_VERSION y Tag::OS_PATCHLEVEL. Los clientes de keymaster2 llaman a este método después de abrir el HAL, pero antes de llamar a cualquier otro método. Si se llama a cualquier otro método antes de configurar, el TA devuelve ErrorCode::KEYMASTER_NOT_CONFIGURED.

La primera vez que se llama a configure después de que se inicia el dispositivo, debe verificar que la información de la versión proporcionada coincida con la que proporcionó el bootloader. Si la información de la versión no coincide, configure devuelve ErrorCode::INVALID_ARGUMENT, y todos los demás métodos de Keymaster siguen devolviendo ErrorCode::KEYMASTER_NOT_CONFIGURED. Si la información coincide, configure devuelve ErrorCode::OK y otros métodos de Keymaster comienzan a funcionar con normalidad.

Las llamadas posteriores a configure devuelven el mismo valor que la primera llamada y no cambian el estado de Keymaster.

Dado que el sistema llama a configure, cuyo contenido se pretende validar, hay una pequeña ventana de oportunidad para que un atacante comprometa la imagen del sistema y la obligue a proporcionar información de la versión que coincida con la imagen de arranque, pero que no sea la versión real del sistema. La combinación de la verificación de la imagen de arranque, la validación de dm-verity del contenido de la imagen del sistema y el hecho de que se llama a configure muy temprano en el arranque del sistema deberían dificultar la explotación de esta oportunidad.