Almacén de claves respaldado por hardware

La disponibilidad de un entorno de ejecución confiable en un sistema en un chip (SoC) ofrece una oportunidad para que los dispositivos Android brinden servicios de seguridad sólidos respaldados por hardware al sistema operativo Android, a servicios de plataforma e incluso a aplicaciones de terceros. Los desarrolladores que busquen extensiones específicas de Android deben ir a android.security.keystore .

Antes de Android 6.0, Android ya tenía una API de servicios criptográficos simple respaldada por hardware, proporcionada por las versiones 0.2 y 0.3 de Keymaster Hardware Abstraction Layer (HAL). Keystore proporcionó operaciones de verificación y firma digital, además de generación e importación de pares de claves de firma asimétrica. Esto ya está implementado en muchos dispositivos, pero hay muchos objetivos de seguridad que no se pueden lograr fácilmente con solo una API de firma. Keystore en Android 6.0 amplió la API Keystore para proporcionar una gama más amplia de capacidades.

En Android 6.0, Keystore agregó primitivas criptográficas simétricas , AES y HMAC, y un sistema de control de acceso para claves respaldadas por hardware. Los controles de acceso se especifican durante la generación de claves y se aplican durante la vida útil de la clave. Las claves se pueden restringir para que sean utilizables solo después de que el usuario haya sido autenticado y solo para propósitos específicos o con parámetros criptográficos específicos. Para obtener más información, consulte las páginas de Etiquetas y funciones de autorización.

Además de ampliar la gama de primitivas criptográficas, Keystore en Android 6.0 agregó lo siguiente:

  • Un esquema de control de uso para permitir que el uso de claves sea limitado y mitigar el riesgo de comprometer la seguridad debido al uso indebido de las claves.
  • Un esquema de control de acceso para permitir la restricción de claves a usuarios y clientes específicos y un rango de tiempo definido

En Android 7.0, Keymaster 2 agregó soporte para atestación de claves y vinculación de versiones. La atestación de clave proporciona certificados de clave pública que contienen una descripción detallada de la clave y sus controles de acceso, para que la existencia de la clave en hardware seguro y su configuración sean verificables de forma remota.

El enlace de versión vincula las claves al sistema operativo y a la versión del nivel de parche. Esto garantiza que un atacante que descubre una debilidad en una versión anterior del sistema o en el software TEE no pueda revertir un dispositivo a la versión vulnerable y usar claves creadas con la versión más nueva. Además, cuando se utiliza una clave con una versión y un nivel de parche determinados en un dispositivo que se ha actualizado a una versión o nivel de parche más reciente, la clave se actualiza antes de poder usarse y la versión anterior de la clave se invalida. A medida que se actualiza el dispositivo, las claves avanzan junto con el dispositivo, pero cualquier reversión del dispositivo a una versión anterior hace que las claves queden inutilizables.

En Android 8.0, Keymaster 3 pasó de la capa de abstracción de hardware (HAL) de estructura C de estilo antiguo a la interfaz HAL de C++ generada a partir de una definición en el nuevo lenguaje de definición de interfaz de hardware (HIDL). Como parte del cambio, muchos de los tipos de argumentos cambiaron, aunque los tipos y métodos tienen una correspondencia uno a uno con los tipos antiguos y los métodos de estructura HAL. Consulte la página Funciones para obtener más detalles.

Además de esta revisión de la interfaz, Android 8.0 amplió la función de certificación de Keymaster 2 para admitir la certificación de identificación . La certificación de identificación proporciona un mecanismo limitado y opcional para certificar firmemente los identificadores de hardware, como el número de serie del dispositivo, el nombre del producto y la identificación del teléfono (IMEI/MEID). Para implementar esta adición, Android 8.0 cambió el esquema de certificación ASN.1 para agregar una certificación de ID. Las implementaciones de Keymaster necesitan encontrar alguna forma segura de recuperar los elementos de datos relevantes, así como definir un mecanismo para deshabilitar la función de forma segura y permanente.

En Android 9, las actualizaciones incluyeron:

  • Actualización a Keymaster 4
  • Soporte para elementos seguros integrados
  • Soporte para importación segura de claves
  • Soporte para cifrado 3DES
  • Cambios en el enlace de versiones para que boot.img y system.img tengan versiones configuradas por separado para permitir actualizaciones independientes

Glosario

A continuación se ofrece una descripción general rápida de los componentes del almacén de claves y sus relaciones.

AndroidKeystore es la API de Android Framework y el componente utilizado por las aplicaciones para acceder a la funcionalidad del almacén de claves. Se implementa como una extensión de las API estándar de arquitectura de criptografía de Java y consta de código Java que se ejecuta en el propio espacio de proceso de la aplicación. AndroidKeystore cumple con las solicitudes de aplicaciones para el comportamiento del almacén de claves reenviándolas al demonio del almacén de claves.

El demonio del almacén de claves es un demonio del sistema Android que proporciona acceso a todas las funciones del almacén de claves a través de una API de Binder . Es responsable de almacenar "blobs de claves", que contienen el material de clave secreta real, cifrado para que Keystore pueda almacenarlos pero no usarlos ni revelarlos.

keymasterd es un servidor HIDL que proporciona acceso a Keymaster TA. (Este nombre no está estandarizado y tiene fines conceptuales).

Keymaster TA (aplicación confiable) es el software que se ejecuta en un contexto seguro, generalmente en TrustZone en un SoC ARM, que proporciona todas las operaciones seguras del almacén de claves, tiene acceso al material clave sin procesar y valida todas las condiciones de control de acceso a las claves. , etc.

LockSettingsService es el componente del sistema Android responsable de la autenticación del usuario, tanto por contraseña como por huella digital. No forma parte de Keystore, pero es relevante porque muchas operaciones de claves de Keystore requieren autenticación del usuario. LockSettingsService interactúa con Gatekeeper TA y Fingerprint TA para obtener tokens de autenticación, que proporciona al demonio del almacén de claves y que, en última instancia, son consumidos por la aplicación Keymaster TA.

Gatekeeper TA (aplicación confiable) es otro componente que se ejecuta en el contexto seguro, que es responsable de autenticar las contraseñas de los usuarios y generar tokens de autenticación utilizados para demostrarle al Keymaster TA que se realizó una autenticación para un usuario en particular en un momento determinado.

Fingerprint TA (aplicación confiable) es otro componente que se ejecuta en el contexto seguro y es responsable de autenticar las huellas digitales del usuario y generar tokens de autenticación utilizados para demostrarle al Keymaster TA que se realizó una autenticación para un usuario en particular en un momento determinado.

Arquitectura

La API de Android Keystore y el Keymaster HAL subyacente proporcionan un conjunto básico pero adecuado de primitivas criptográficas para permitir la implementación de protocolos utilizando claves respaldadas por hardware y de acceso controlado.

Keymaster HAL es una biblioteca cargable dinámicamente proporcionada por OEM utilizada por el servicio Keystore para proporcionar servicios criptográficos respaldados por hardware. Para mantener la seguridad, las implementaciones de HAL no realizan ninguna operación confidencial en el espacio del usuario, ni siquiera en el espacio del kernel. Las operaciones confidenciales se delegan a un procesador seguro al que se accede a través de alguna interfaz del kernel. La arquitectura resultante se ve así:

Acceso a Keymaster

Figura 1. Acceso a Keymaster

Dentro de un dispositivo Android, el "cliente" de Keymaster HAL consta de varias capas (por ejemplo, aplicación, marco, demonio de almacén de claves), pero eso se puede ignorar a los efectos de este documento. Esto significa que la API HAL de Keymaster descrita es de bajo nivel, utilizada por componentes internos de la plataforma y no expuesta a los desarrolladores de aplicaciones. La API de nivel superior se describe en el sitio para desarrolladores de Android .

El propósito de Keymaster HAL no es implementar algoritmos sensibles a la seguridad, sino solo organizar y desorganizar solicitudes para el mundo seguro. El formato del cable está definido por la implementación.

Compatibilidad con versiones anteriores

El Keymaster 1 HAL es completamente incompatible con los HAL lanzados anteriormente, por ejemplo, Keymaster 0.2 y 0.3. Para facilitar la interoperabilidad en dispositivos que ejecutan Android 5.0 y versiones anteriores que se lanzaron con los Keymaster HAL más antiguos, Keystore proporciona un adaptador que implementa Keymaster 1 HAL con llamadas a la biblioteca de hardware existente. El resultado no puede proporcionar toda la gama de funciones del Keymaster 1 HAL. En particular, solo admite algoritmos RSA y ECDSA, y toda la aplicación de autorización de claves la realiza el adaptador, en el mundo no seguro.

Keymaster 2 simplificó aún más la interfaz HAL eliminando los métodos get_supported_* y permitiendo que el método finish() acepte entradas. Esto reduce el número de viajes de ida y vuelta al TEE en los casos en que la entrada está disponible toda a la vez y simplifica la implementación del descifrado AEAD.

En Android 8.0, Keymaster 3 pasó de la estructura HAL de estilo antiguo a la interfaz HAL de C++ generada a partir de una definición en el nuevo lenguaje de definición de interfaz de hardware (HIDL). Se crea una implementación HAL de nuevo estilo subclasificando la clase IKeymasterDevice generada e implementando los métodos virtuales puros. Como parte del cambio, muchos de los tipos de argumentos han cambiado, aunque los tipos y métodos tienen una correspondencia uno a uno con los tipos antiguos y los métodos de estructura HAL.

Descripción general de HIDL

El lenguaje de definición de interfaz de hardware (HIDL) proporciona un mecanismo de implementación independiente del lenguaje para especificar interfaces de hardware. Las herramientas HIDL actualmente admiten la generación de interfaces C++ y Java. Se espera que la mayoría de los implementadores de Trusted Execution Environment (TEE) encuentren las herramientas de C++ más convenientes, por lo que este documento analiza solo la representación de C++.

Las interfaces HIDL constan de un conjunto de métodos, expresados ​​como:

  methodName(INPUT ARGUMENTS) generates (RESULT ARGUMENTS);

Hay varios tipos predefinidos y los HAL pueden definir nuevos tipos enumerados y de estructura. Para obtener más detalles sobre HIDL, consulte la sección Referencia .

Un método de ejemplo de Keymaster 3 IKeymasterDevice.hal es:

generateKey(vec<KeyParameter> keyParams)
        generates(ErrorCode error, vec<uint8_t> keyBlob,
                  KeyCharacteristics keyCharacteristics);

Este es el equivalente a lo siguiente del HAL keymaster2:

keymaster_error_t (*generate_key)(
        const struct keymaster2_device* dev,
        const keymaster_key_param_set_t* params,
        keymaster_key_blob_t* key_blob,
        keymaster_key_characteristics_t* characteristics);

En la versión HIDL, el argumento dev se elimina porque está implícito. El argumento params ya no es una estructura que contiene un puntero que hace referencia a una matriz de objetos key_parameter_t , sino un vec (vector) que contiene objetos KeyParameter . Los valores devueltos se enumeran en la cláusula " generates ", incluido un vector de valores uint8_t para el blob clave.

El método virtual C++ generado por el compilador HIDL es:

Return<void> generateKey(const hidl_vec<KeyParameter>& keyParams,
                         generateKey_cb _hidl_cb) override;

Donde generateKey_cb es un puntero de función definido como:

std::function<void(ErrorCode error, const hidl_vec<uint8_t>& keyBlob,
                   const KeyCharacteristics& keyCharacteristics)>

Es decir, generateKey_cb es una función que toma los valores de retorno enumerados en la cláusula de generación. La clase de implementación HAL anula este método generateKey y llama al puntero de función generateKey_cb para devolver el resultado de la operación a la persona que llama. Tenga en cuenta que la llamada al puntero de función es sincrónica . La persona que llama llama generateKey y generateKey llama al puntero de función proporcionado, que se ejecuta hasta su finalización, devolviendo el control a la implementación generateKey , que luego regresa a la persona que llama.

Para obtener un ejemplo detallado, consulte la implementación predeterminada en hardware/interfaces/keymaster/3.0/default/KeymasterDevice.cpp . La implementación predeterminada proporciona compatibilidad con versiones anteriores para dispositivos con HALS keymaster0, keymaster1 o keymaster2 de estilo antiguo.

Control de acceso

La regla más básica del control de acceso al almacén de claves es que cada aplicación tiene su propio espacio de nombres. Pero para cada regla hay una excepción. Keystore tiene algunos mapas codificados que permiten que ciertos componentes del sistema accedan a otros espacios de nombres. Este es un instrumento muy contundente ya que le da a un componente control total sobre otro espacio de nombres. Y luego está la cuestión de los componentes del proveedor como clientes de Keystore. Actualmente no tenemos forma de establecer un espacio de nombres para los componentes del proveedor, por ejemplo, el solicitante WPA.

Para adaptarse a los componentes de los proveedores y generalizar el control de acceso sin excepciones codificadas, Keystore 2.0 introduce dominios y espacios de nombres SELinux.

Dominios de almacén de claves

Con los dominios de Keystore, podemos desacoplar los espacios de nombres de los UID. Los clientes que acceden a una clave en Keystore deben especificar el dominio, el espacio de nombres y el alias al que desean acceder. En función de esta tupla y la identidad de la persona que llama, podemos determinar a qué clave quiere acceder la persona que llama y si tiene los permisos adecuados.

Introducimos cinco parámetros de dominio que rigen cómo se puede acceder a las claves. Controlan la semántica del parámetro de espacio de nombres del descriptor de clave y cómo se realiza el control de acceso.

  • DOMAIN_APP : el dominio de la aplicación cubre el comportamiento heredado. El SPI del almacén de claves de Java utiliza este dominio de forma predeterminada. Cuando se utiliza este dominio, el argumento del espacio de nombres se ignora y en su lugar se utiliza el UID de la persona que llama. El acceso a este dominio está controlado por la etiqueta Keystore de la clase keystore_key en la política SELinux.
  • DOMAIN_SELINUX : este dominio indica que el espacio de nombres tiene una etiqueta en la política SELinux. El parámetro del espacio de nombres se busca y se traduce a un contexto de destino, y se realiza una verificación de permisos para el contexto SELinux que llama para la clase keystore_key . Cuando se ha establecido el permiso para la operación determinada, se utiliza la tupla completa para la búsqueda de claves.
  • DOMAIN_GRANT : el dominio de concesión indica que el parámetro de espacio de nombres es un identificador de concesión. El parámetro alias se ignora. Las comprobaciones de SELinux se realizan cuando se crea la concesión. El control de acceso adicional solo verifica si el UID de la persona que llama coincide con el UID del beneficiario de la concesión solicitada.
  • DOMAIN_KEY_ID : este dominio indica que el parámetro del espacio de nombres es una identificación de clave única. Es posible que la clave en sí se haya creado con DOMAIN_APP o DOMAIN_SELINUX . La verificación de permisos se realiza después de que el domain y el namespace se hayan cargado desde la base de datos de claves de la misma manera que si el blob fuera cargado por la tupla de dominio, espacio de nombres y alias. La razón fundamental para el dominio de identificación clave es la continuidad. Al acceder a una clave por alias, las llamadas posteriores pueden operar en claves diferentes, porque es posible que se haya generado o importado una nueva clave y vinculada a este alias. La identificación de la clave, sin embargo, nunca cambia. Entonces, cuando se usa una clave por identificación de clave después de haberla cargado desde la base de datos del almacén de claves usando el alias una vez, uno puede estar seguro de que es la misma clave siempre que la identificación de clave aún exista. Esta funcionalidad no está expuesta a los desarrolladores de aplicaciones. En cambio, se utiliza dentro del SPI del almacén de claves de Android para proporcionar una experiencia más consistente incluso cuando se usa simultáneamente de manera insegura.
  • DOMAIN_BLOB : el dominio del blob indica que el autor de la llamada administra el blob por sí mismo. Esto se utiliza para clientes que necesitan acceder al almacén de claves antes de montar la partición de datos. El blob de claves se incluye en el campo blob del descriptor de claves.

Al utilizar el dominio SELinux, podemos brindar a los componentes del proveedor acceso a espacios de nombres de almacén de claves muy específicos que pueden ser compartidos por componentes del sistema, como el cuadro de diálogo de configuración.

Política SELinux para keystore_key

Las etiquetas de espacio de nombres se configuran mediante el archivo keystore2_key_context .
Cada línea de estos archivos asigna una identificación de espacio de nombres numérico a una etiqueta SELinux. Por ejemplo,

# wifi_key is a keystore2_key namespace intended to be used by wpa supplicant and
# Settings to share keystore keys.
102            u:object_r:wifi_key:s0

Después de haber configurado un nuevo espacio de nombres clave de esta manera, podemos darle acceso agregando una política adecuada. Por ejemplo, para permitir que wpa_supplicant obtenga y use claves en el nuevo espacio de nombres, agregaríamos la siguiente línea a hal_wifi_supplicant.te :

allow hal_wifi_supplicant wifi_key:keystore2_key { get, use };

Después de configurar el nuevo espacio de nombres, AndroidKeyStore se puede utilizar casi como de costumbre. La única diferencia es que se debe especificar el ID del espacio de nombres. Para cargar e importar claves desde y hacia Keystore, la identificación del espacio de nombres se especifica mediante AndroidKeyStoreLoadStoreParameter . Por ejemplo,

import android.security.keystore2.AndroidKeyStoreLoadStoreParameter;
import java.security.KeyStore;

KeyStore keystore = KeyStore.getInstance("AndroidKeyStore");
keystore.load(new AndroidKeyStoreLoadStoreParameter(102));

Para generar una clave en un espacio de nombres determinado, la identificación del espacio de nombres se debe proporcionar usando KeyGenParameterSpec.Builder#setNamespace():

import android.security.keystore.KeyGenParameterSpec;
KeyGenParameterSpec.Builder specBuilder = new KeyGenParameterSpec.Builder();
specBuilder.setNamespace(102);

Los siguientes archivos de contexto se pueden utilizar para configurar los espacios de nombres SELinux de Keystore 2.0. Cada partición tiene un rango reservado diferente de 10.000 identificadores de espacio de nombres para evitar colisiones.

Dividir Rango Archivos de configuración
Sistema 0...9999
/system/etc/selinux/keystore2_key_contexts, /plat_keystore2_key_contexts
Sistema extendido 10.000... 19.999
/system_ext/etc/selinux/system_ext_keystore2_key_contexts, /system_ext_keystore2_key_contexts
Producto 20.000... 29.999
/product/etc/selinux/product_keystore2_key_contexts, /product_keystore2_key_contexts
Proveedor 30.000... 39.999
/vendor/etc/selinux/vendor_keystore2_key_contexts, /vendor_keystore2_key_contexts

El cliente solicita la clave solicitando el dominio SELinux y el espacio de nombres virtual deseado, en este caso "wifi_key" , por su identificación numérica.

Por encima de eso, se han definido los siguientes espacios de nombres. Si reemplazan reglas especiales, la siguiente tabla indica el UID al que solían corresponder.

ID de espacio de nombres Etiqueta de política SE UID Descripción
0 su_key N / A Clave de superusuario. Solo se usa para probar en compilaciones de usuario y eng. No es relevante en compilaciones de usuarios.
1 clave_shell N / A Espacio de nombres disponible para shell. Se utiliza principalmente para pruebas, pero también se puede utilizar en compilaciones de usuarios desde la línea de comandos.
100 vold_key N / A Diseñado para ser utilizado por vold.
101 clave_odsing N / A Utilizado por el demonio de firma del dispositivo.
102 clave_wifi AID_WIFI(1010) Utilizado por el sybsystem Wifi de Android, incluido wpa_supplicant.
120 resume_on_reboot_key SISTEMA_AID(1000) Utilizado por el servidor del sistema de Android para admitir la reanudación al reiniciar.

Vectores de acceso

La clase SELinux keystore_key ha envejecido bastante y algunos de los permisos, como verify o sign han perdido su significado. Aquí está el nuevo conjunto de permisos, keystore2_key , que Keystore 2.0 aplicará.

Permiso Significado
delete Comprobado al eliminar claves del almacén de claves.
get_info Se verifica cuando se solicitan los metadatos de una clave.
grant La persona que llama necesita este permiso para crear una concesión a la clave en el contexto de destino.
manage_blob La persona que llama puede usar DOMAIN_BLOB en el espacio de nombres SELinux dado, administrando así los blobs por sí mismo. Esto es específicamente útil para vold.
rebind Este permiso controla si un alias puede revertirse a una nueva clave. Esto es necesario para la inserción e implica que se eliminará la clave previamente vinculada. Es básicamente un permiso de inserción, pero captura mejor la semántica del almacén de claves.
req_forced_op Los clientes con este permiso pueden crear operaciones no podables y la creación de operaciones nunca falla a menos que todas las ranuras de operación estén ocupadas por operaciones no podables.
update Requerido para actualizar el subcomponente de una clave.
use Se verifica al crear una operación Keymint que utiliza el material clave, por ejemplo, para firmar, cifrar/descifrar.
use_dev_id Se requiere al generar información de identificación del dispositivo, como la certificación de identificación del dispositivo.

Además, dividimos un conjunto de permisos de almacén de claves no específicos de claves en el keystore2 de la clase de seguridad SELinux:

Permiso Significado
add_auth Requerido por el proveedor de autenticación, como Gatekeeper o BiometricsManager, para agregar tokens de autenticación.
clear_ns Anteriormente clear_uid, este permiso permite a quienes no son propietarios de un espacio de nombres eliminar todas las claves de ese espacio de nombres.
list Requerido por el sistema para enumerar claves según diversas propiedades, como propiedad o limitación de autenticación. Las personas que llaman y enumeran sus propios espacios de nombres no requieren este permiso. Esto está cubierto por el permiso get_info .
lock Este permiso permite bloquear el almacén de claves, es decir, desalojar la clave maestra, de modo que las claves vinculadas a la autenticación se vuelvan inutilizables e imposibles de crear.
reset Este permiso permite restablecer Keystore a los valores predeterminados de fábrica, eliminando todas las claves que no son vitales para el funcionamiento del sistema operativo Android.
unlock Este permiso es necesario para intentar desbloquear la clave maestra para claves vinculadas a autenticación.