Vinculación de versión

En Keymaster 1, todas las claves de Keymaster estaban vinculadas criptográficamente a la raíz de confianza del dispositivo o a la clave de inicio 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 vulnerabilidad en una versión antigua del sistema o del software de 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 esta manera, a medida que se actualiza el dispositivo, las claves se actualizan junto con él, pero cualquier reversión del dispositivo a una versión anterior hace que las claves no se puedan usar.

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. Gracias a ello, se actualiza independientemente cada partición y, al mismo tiempo, se ofrece protección contra reversiones.

En Android 9, las particiones boot, system y vendor tienen su propio nivel de parche.

  • Los dispositivos con inicio verificado de Android (AVB) pueden colocar todos los niveles de parches 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 hashtree) de una partición determinada.
  • En dispositivos sin AVB:
    • Las implementaciones de inicio verificado deben proporcionar un hash de los metadatos de la versión al bootloader para 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 bootloader 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 versión para 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 parches de forma independiente. Las claves se pueden usar si toda la información de la versión coincide con los valores asociados con una clave, y IKeymaster::upgradeDevice() se traslada a un nivel de parche más alto si es necesario.

Cambios en 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 implementaciones de Keymaster 2 y versiones posteriores agregan automáticamente las etiquetas de versión a todas las claves generadas (o actualizadas) recientemente. Además, cualquier intento de usar una clave que no tenga una versión o un nivel de parche del SO que coincida con la versión o el nivel de parche del SO actual se rechaza con ErrorCode::KEY_REQUIRES_UPGRADE.

Tag::OS_VERSION es un valor UINT que representa las partes principales, secundarias y subsecundarias 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 UINT que representa el año y el mes de la última actualización del sistema como AAAAMM, en el que 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 al 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 claves, si se proporcionaron durante la generación.
  • upgradedKeyBlob es el parámetro de salida, que se usa para mostrar 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 muestra ErrorCode::INVALID_KEY_BLOB. Si se llama con una clave cuyo nivel de parche es mayor que el valor del sistema actual, muestra ErrorCode::INVALID_ARGUMENT. Si se lo llama con una clave cuya versión del SO es mayor que el valor actual del sistema y el valor del sistema es distinto de cero, muestra ErrorCode::INVALID_ARGUMENT. Se permiten las actualizaciones de versión del SO de un valor distinto de cero a cero. En caso de errores de comunicación con el mundo seguro, muestra un valor de error adecuado (por ejemplo, ErrorCode::SECURE_HW_ACCESS_DENIED o ErrorCode::SECURE_HW_BUSY). De lo contrario, muestra ErrorCode::OK y muestra un nuevo blob de clave en upgradedKeyBlob.

keyBlobToUpgrade sigue siendo válido después de la llamada a upgradeKey y, en teoría, se podría volver a usar si se degradara el dispositivo. En la práctica, el almacén de claves suele llamar 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 forma segura la versión actual del SO y el nivel de parche (información de la versión) y asegurarse de que la información que recibe coincida de forma clara con la información del 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 inicio completa automáticamente este campo. Los OEM y los implementadores de la TA de Keymaster deben trabajar juntos para modificar los bootloaders del dispositivo 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 información de versión al 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 de configuración al 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 muestra 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 muestra ErrorCode::INVALID_ARGUMENT y todos los demás métodos de Keymaster siguen mostrando ErrorCode::KEYMASTER_NOT_CONFIGURED. Si la información coincide, configure muestra ErrorCode::OK y otros métodos de Keymaster comienzan a funcionar con normalidad.

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

Debido a que el sistema cuyo contenido se pretende validar llama a configure, hay una ventana de oportunidad limitada para que un atacante comprometa la imagen del sistema y la obligue a proporcionar información de versión que coincida con la imagen de inicio, pero que no sea la versión real del sistema. La combinación de la verificación de la imagen de inicio, la validación de dm-verity del contenido de la imagen del sistema y el hecho de que se llame a configure muy al principio del inicio del sistema debería dificultar el aprovechamiento de esta ventana de oportunidad.