La disponibilidad de un entorno de ejecución confiable en un sistema en un chip (SoC) ofrece una oportunidad para que los dispositivos Android proporcionen servicios de seguridad sólidos respaldados por hardware al SO Android, a los servicios de la plataforma y hasta a las apps de terceros. Los desarrolladores que buscan extensiones específicas de Android deben ir a android.security.keystore.
Antes de Android 6.0, Android ya tenía una API de servicios de criptografía simple con copia de seguridad en hardware, proporcionada por las versiones 0.2 y 0.3 de la capa de abstracción de hardware (HAL) de Keymaster. El almacén de claves proporcionó operaciones de firma y verificación digitales, además de la generación e importación de pares de claves de firma asimétrica. Esto ya se implementó 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 extendió la API de Keystore para proporcionar una gama más amplia de funciones.
En Android 6.0, Keystore agregó primitivos de criptografía simétrica, AES y HMAC, y un sistema de control de acceso para claves con copia de seguridad en hardware. Los controles de acceso se especifican durante la generación de claves y se aplican durante toda su vida útil. Las claves se pueden restringir para que solo se puedan usar después de que se haya autenticado al usuario y solo para fines específicos o con parámetros criptográficos específicos. Para obtener más información, consulta la página Etiquetas de autorización.
Además de expandir el rango de primitivas criptográficas, Keystore en Android 6.0 agregó lo siguiente:
- Un esquema de control de uso para permitir que se limite el uso de claves y mitigar el riesgo de vulneración de la seguridad debido al uso inadecuado de las claves
- Un esquema de control de acceso para habilitar la restricción de claves a usuarios, clientes y un período definidos
En Android 7.0, Keymaster 2 agregó compatibilidad con la certificación de claves y la vinculación de versiones. La certificación de claves 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 se puedan verificar de forma remota.
La vinculación de versiones vincula las claves al sistema operativo y a la versión del nivel de parche. Esto garantiza que un atacante que descubra una debilidad en una versión anterior 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 reciente, la clave se actualiza antes de que se pueda usar y se invalida la versión anterior de la clave. A medida que se actualiza el dispositivo, las claves se "retienen" junto con el dispositivo, pero cualquier reversión del dispositivo a una versión anterior hace que las claves no se puedan usar.
En Android 8.0, Keymaster 3 realizó la transición de la capa de abstracción de hardware (HAL) de estructura C de estilo antiguo a la interfaz de 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 los métodos tienen una correspondencia uno a uno con los tipos anteriores y los métodos de la estructura HAL.
Además de esta revisión de la interfaz, Android 8.0 extendió la función de certificación de Keymaster 2 para admitir la certificación de ID. La certificación de ID proporciona un mecanismo limitado y opcional para certificar de forma sólida los identificadores de hardware, como el número de serie del dispositivo, el nombre del producto y el ID de teléfono (IMEI / MEID). Para implementar esta adición, Android 8.0 cambió el esquema de certificación ASN.1 para agregar la certificación de ID. Las implementaciones de Keymaster deben encontrar una forma segura de recuperar los elementos de datos relevantes, así como definir un mecanismo para inhabilitar la función de forma segura y permanente.
En Android 9, las actualizaciones incluyeron lo siguiente:
- Actualización a Keymaster 4
- Compatibilidad con elementos seguros incorporados
- Compatibilidad con la importación de claves seguras
- Compatibilidad con la encriptación 3DES
- Se realizaron cambios en la vinculación de versiones para que boot.img y system.img tengan versiones configuradas por separado para permitir actualizaciones independientes.
Glosario
Esta es una descripción general rápida de los componentes del almacén de claves y sus relaciones.
AndroidKeystore es la API y el componente del framework de Android que usan las apps para acceder a la funcionalidad de Keystore. Se implementa como una extensión de las APIs estándar de la arquitectura de criptografía de Java y consta de código Java que se ejecuta en el espacio de proceso de la app. AndroidKeystore
entrega las solicitudes de la app para el comportamiento del almacén de claves reenviándolas al daemon del almacén de claves.
El daemon de Keystore es un daemon del sistema Android que proporciona acceso a todas las funciones de Keystore con una API de Binder. Es responsable de almacenar "blobs de claves", que contienen el material de clave secreta real, encriptado para que Keystore pueda almacenarlos, pero no usarlos ni revelarlos.
keymasterd es un servidor HIDL que proporciona acceso al TA de Keymaster. (este nombre no está estandarizado y es solo conceptual).
El TA de Keymaster (app de confianza) es el software que se ejecuta en un contexto seguro, con mayor frecuencia en TrustZone en un SoC ARM, que proporciona todas las operaciones seguras del almacén de claves, tiene acceso al material de clave sin procesar, valida todas las condiciones de control de acceso en las claves, etcétera.
LockSettingsService es el componente del sistema Android responsable de la autenticación del usuario, tanto con contraseña como con huellas dactilares. No forma parte de Keystore, pero es relevante porque muchas operaciones de claves de Keystore requieren la autenticación del usuario. LockSettingsService
interactúa con la TA de Gatekeeper y la TA de Fingerprint para obtener tokens de autenticación, que proporciona al daemon del almacén de claves y que, en última instancia, consume la app de la TA de Keymaster.
El TA de Gatekeeper (app de confianza) es otro componente que se ejecuta en el contexto seguro, que es responsable de autenticar las contraseñas del usuario y generar tokens de autenticación que se usan para demostrarle al TA de Keymaster que se realizó una autenticación para un usuario en particular en un momento determinado.
El TA de huellas digitales (app de confianza) es otro componente que se ejecuta en el contexto seguro y que se encarga de autenticar las huellas digitales del usuario y generar tokens de autenticación que se usan para demostrarle al TA de Keymaster que se realizó una autenticación para un usuario en particular en un momento determinado.
Arquitectura
La API de Android Keystore y el HAL de Keymaster subyacente proporcionan un conjunto básico, pero adecuado, de primitivas criptográficas para permitir la implementación de protocolos con claves con copia de seguridad en hardware y control de acceso.
El HAL de Keymaster es una biblioteca que se puede cargar de forma dinámica y que proporciona el OEM, que usa el servicio de Keystore para proporcionar servicios criptográficos respaldados por hardware. Para mantener la seguridad, las implementaciones de HAL no realizan ninguna operación sensible en el espacio del usuario ni en el espacio del kernel. Las operaciones sensibles se delegan a un procesador seguro al que se accede a través de alguna interfaz de kernel. La arquitectura resultante se ve así:
![Acceso a Keymaster](https://source.android.google.cn/static/docs/security/images/access-to-keymaster.png?authuser=3&hl=es-419)
Figura 1: Acceso a Keymaster
Dentro de un dispositivo Android, el "cliente" de la HAL de Keymaster consta de varias capas (por ejemplo, app, framework, daemon de Keystore), pero se puede ignorar para los fines de este documento. Esto significa que la API de HAL de Keymaster que se describe es de bajo nivel, que usan los componentes internos de la plataforma y que no se expone a los desarrolladores de apps. La API de nivel superior se describe en el sitio de Android Developers.
El propósito de la HAL de Keymaster no es implementar los algoritmos sensibles a la seguridad, sino solo organizar y desorganizar las solicitudes en el mundo seguro. El formato de cable se define según la implementación.
Compatibilidad con versiones anteriores
El HAL de Keymaster 1 es completamente incompatible con los HALs 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 HAL de Keymaster más antiguos, el almacén de claves proporciona un adaptador que implementa el HAL de Keymaster 1 con llamadas a la biblioteca de hardware existente. El resultado no puede proporcionar la gama completa de funciones en el HAL de Keymaster 1. En particular, solo admite algoritmos RSA y ECDSA, y el adaptador realiza toda la aplicación forzosa de la autorización de claves en el entorno no seguro.
Keymaster 2 simplificó aún más la interfaz de HAL quitando los métodos get_supported_*
y permitiendo que el método finish()
acepte entradas. Esto reduce la cantidad de ida y vuelta al TEE en los casos en que la entrada está disponible al mismo tiempo y simplifica la implementación de la desencriptación de AEAD.
En Android 8.0, Keymaster 3 realizó la transición de la HAL de estructura C de estilo antiguo a la interfaz de HAL de C++ generada a partir de una definición en el nuevo lenguaje de definición de interfaz de hardware (HIDL). Para crear una implementación de HAL de estilo nuevo, se crea una subclase de la clase IKeymasterDevice
generada y se implementan los métodos virtuales puros. Como parte del cambio, muchos de los tipos de argumentos cambiaron, aunque los tipos y los métodos tienen una correspondencia uno a uno con los tipos anteriores y los métodos de la estructura HAL.
Descripción general de HIDL
El lenguaje de definición de la interfaz de hardware (HIDL) proporciona un mecanismo de implementación independiente del lenguaje para especificar interfaces de hardware. Actualmente, las herramientas de HIDL admiten la generación de interfaces de C++ y Java. Esperamos que la mayoría de los implementadores de entornos de ejecución confiables (TEE) encuentren las herramientas de C++ más convenientes, por lo que en esta página solo se analiza la representación de C++.
Las interfaces de HIDL consisten en un conjunto de métodos, expresados de la siguiente manera:
methodName(INPUT ARGUMENTS) generates (RESULT ARGUMENTS);
Existen varios tipos predefinidos, y los HAL pueden definir nuevos tipos enumerados y de estructura. Para obtener más detalles sobre HIDL, consulta la sección de referencia.
Un ejemplo de método de IKeymasterDevice.hal
de Keymaster 3 es el siguiente:
generateKey(vec<KeyParameter> keyParams) generates(ErrorCode error, vec<uint8_t> keyBlob, KeyCharacteristics keyCharacteristics);
Esto es equivalente a lo siguiente del HAL de 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 de HIDL, se quita el argumento dev
, ya que es implícito. El argumento params
ya no es un struct que contiene un puntero que hace referencia a un array de objetos key_parameter_t
, sino un vec
(vector) que contiene objetos KeyParameter
. Los valores que se muestran se enumeran en la cláusula "generates
", incluido un vector de valores uint8_t
para el blob de clave.
El método virtual C++ que genera el compilador de HIDL es el siguiente:
Return<void> generateKey(const hidl_vec<KeyParameter>& keyParams, generateKey_cb _hidl_cb) override;
En el ejemplo anterior, generateKey_cb
es un puntero de función definido de la siguiente manera:
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 que se muestran en la cláusula generate. La clase de implementación de HAL anula este método generateKey
y llama al puntero de función generateKey_cb
para mostrar el resultado de la operación al llamador. Ten en cuenta que la llamada al puntero de función es síncrona. El llamador llama a generateKey
y generateKey
llama al puntero de función proporcionado, que se ejecuta hasta completarse y devuelve el control a la implementación de generateKey
, que luego se devuelve al llamador.
Para ver un ejemplo detallado, consulta 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 de Keystore es que cada app tiene su propio espacio de nombres. Sin embargo, para cada regla, hay una excepción. El almacén de claves 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 otorga a un componente el control total sobre otro espacio de nombres. Además, está el tema de los componentes del proveedor como clientes del almacén de claves. Actualmente, no tenemos forma de establecer un espacio de nombres para los componentes del proveedor, por ejemplo, el solicitante de WPA.
Para admitir componentes de proveedores y generalizar el control de acceso sin excepciones hard-coded, Keystore 2.0 presenta dominios y espacios de nombres de SELinux.
Dominios del 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 del emisor, podemos determinar a qué clave quiere acceder el emisor y si tiene los permisos adecuados.
Presentamos cinco parámetros de dominio que rigen la forma en que se puede acceder a las claves. Controlan la semántica del parámetro de espacio de nombres del descriptor de claves y cómo se realiza el control de acceso.
DOMAIN_APP
: El dominio de la app abarca el comportamiento heredado. El SPI de Java Keystore usa este dominio de forma predeterminada. Cuando se usa este dominio, se ignora el argumento de espacio de nombres y, en su lugar, se usa el UID del llamador. El acceso a este dominio está controlado por la etiqueta del almacén de claves para la clasekeystore_key
en la política de SELinux.DOMAIN_SELINUX
: Este dominio indica que el espacio de nombres tiene una etiqueta en la política de SELinux. Se busca el parámetro de espacio de nombres y se traduce a un contexto de destino, y se realiza una verificación de permisos para el contexto de SELinux de llamada de la clasekeystore_key
. Cuando se estableció el permiso para la operación determinada, se usa la tupla completa para la búsqueda de claves.DOMAIN_GRANT
: El dominio de la subvención indica que el parámetro de espacio de nombres es un identificador de subvención. Se ignora el parámetro de alias. Las verificaciones de SELinux se realizan cuando se crea el otorgamiento. El control de acceso adicional solo verifica si el UID del emisor coincide con el UID del beneficiario de la concesión solicitada.DOMAIN_KEY_ID
: Este dominio indica que el parámetro de espacio de nombres es un ID de clave único. Es posible que la clave se haya creado conDOMAIN_APP
oDOMAIN_SELINUX
. La verificación de permisos se realiza después de que se cargarondomain
ynamespace
desde la base de datos de claves de la misma manera que si el blob se cargara con la tupla de dominio, espacio de nombres y alias. La justificación del dominio de ID de clave es la continuidad. Cuando se accede a una clave por alias, las llamadas posteriores pueden operar en claves diferentes, ya que es posible que se haya generado o importado una clave nueva y se haya vinculado a este alias. Sin embargo, el ID de clave nunca cambia. Por lo tanto, cuando se usa una clave por ID de clave después de que se cargó desde la base de datos del almacén de claves con el alias una vez, se puede estar seguro de que es la misma clave, siempre y cuando el ID de clave siga existiendo. Esta funcionalidad no se expone a los desarrolladores de apps. En su lugar, se usa dentro del SPI de Android Keystore para proporcionar una experiencia más coherente, incluso cuando se usa de forma simultánea de manera no segura.DOMAIN_BLOB
: El dominio del blob indica que el llamador administra el blob por sí solo. Se usa para los clientes que necesitan acceder al almacén de claves antes de que se monte la partición de datos. El blob de clave se incluye en el campoblob
del descriptor de clave.
Con el dominio de SELinux, podemos otorgar a los componentes del proveedor acceso a espacios de nombres de Keystore muy específicos que pueden compartir los componentes del sistema, como el diálogo de configuración.
Política de SELinux para keystore_key
Las etiquetas de espacio de nombres se configuran con el archivo keystore2_key_context
.
Cada línea de estos archivos asigna un ID de espacio de nombres numérico a una etiqueta de 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 configurar un nuevo espacio de nombres de claves de esta manera, podemos darle acceso
añadiendo una política adecuada. Por ejemplo, para permitir que wpa_supplicant
obtenga y use claves en el espacio de nombres nuevo, 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 usar 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 el almacén de claves, el ID del espacio de nombres se especifica con 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, se debe proporcionar el ID del espacio de nombres con KeyGenParameterSpec.Builder#setNamespace():
.
import android.security.keystore.KeyGenParameterSpec; KeyGenParameterSpec.Builder specBuilder = new KeyGenParameterSpec.Builder(); specBuilder.setNamespace(102);
Los siguientes archivos de contexto se pueden usar para configurar los espacios de nombres de SELinux de Keystore 2.0. Cada partición tiene un rango reservado diferente de 10,000 ids de espacio de nombres para evitar colisiones.
Partición | Rango | Archivos de configuración |
---|---|---|
Sistema | De 0 a 9,999 | /system/etc/selinux/keystore2_key_contexts, /plat_keystore2_key_contexts |
Sistema extendido | Entre 10,000 y 19,999 | /system_ext/etc/selinux/system_ext_keystore2_key_contexts, /system_ext_keystore2_key_contexts |
Producto | 20,000 a 29,999 | /product/etc/selinux/product_keystore2_key_contexts, /product_keystore2_key_contexts |
Proveedor | 30,000 a 39,999 | /vendor/etc/selinux/vendor_keystore2_key_contexts, /vendor_keystore2_key_contexts |
El cliente solicita la clave solicitando el dominio de SELinux y el espacio de nombres virtual deseado, en este caso "wifi_key"
, por su ID numérico.
Por encima de eso, se definieron los siguientes espacios de nombres. Si reemplazan reglas especiales, la siguiente tabla indica el UID al que solían corresponder.
ID del espacio de nombres | Etiqueta de SEPolicy | UID | Descripción |
---|---|---|---|
0 | su_key | N/A | Clave de superusuario Solo se usa para pruebas en compilaciones de userdebug y eng. No es relevante en las compilaciones de los usuarios. |
1 | shell_key | N/A | Es el espacio de nombres disponible para la shell. Se usa principalmente para pruebas, pero también se puede usar en compilaciones de usuarios desde la línea de comandos. |
100 | vold_key | N/A | Está diseñado para que lo use vold. |
101 | odsing_key | N/A | Es usado por el daemon de firma integrado en el dispositivo. |
102 | wifi_key | AID_WIFI(1010) | Lo usa el subsistema Wifi de Android, incluido wpa_supplicant. |
120 | resume_on_reboot_key | AID_SYSTEM(1000) | El servidor del sistema de Android lo usa para admitir la reanudación en el reinicio. |
Vectores de acceso
La clase keystore_key
de SELinux ya tiene bastante tiempo y algunos de los permisos, como verify
o sign
, perdieron su significado. Este es el nuevo conjunto de permisos, keystore2_key
, que aplica Keystore 2.0.
Permiso | Significado |
---|---|
delete
|
Se verifica cuando se quitan claves del almacén de claves. |
get_info
|
Se verifica cuando se solicitan los metadatos de una clave. |
grant
|
El llamador necesita este permiso para crear una concesión a la clave en el contexto de destino. |
manage_blob
|
El llamador puede usar DOMAIN_BLOB en el espacio de nombres de SELinux determinado y, de esta manera, administrar los blobs por sí solo. Esto es útil, en particular, para vold. |
rebind
|
Este permiso controla si un alias se puede volver a vincular a una clave nueva. Esto es obligatorio para la inserción y implica que se borrará la clave vinculada anteriormente. Es 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 que no se pueden reducir, y la creación de operaciones nunca falla, a menos que todas las ranuras de operación estén ocupadas por operaciones que no se pueden reducir. |
update
|
Es obligatorio para actualizar el subcomponente de una clave. |
use
|
Se verifica cuando se crea una operación de Keymint que usa el material de clave, por ejemplo, para la firma, la encriptación y la desencriptación. |
use_dev_id
|
Obligatorio cuando se genera información de identificación del dispositivo, como la certificación del ID del dispositivo. |
Además, dividimos un conjunto de permisos de almacén de claves no específicos de claves en la clase de seguridad keystore2
de SELinux:
Permiso | Significado |
---|---|
add_auth
|
Es obligatorio para el proveedor de autenticación, como Gatekeeper o BiometricsManager, para agregar tokens de autenticación. |
clear_ns
|
Anteriormente, clear_uid, este permiso permite que un usuario que no es propietario de un espacio de nombres elimine todas las claves de ese espacio de nombres. |
list
|
El sistema lo requiere para enumerar claves por varias propiedades, como la propiedad o la limitación de autenticación. Los llamadores que enumeran sus propios espacios de nombres no requieren este permiso. Esto se incluye en el permiso get_info . |
lock
|
Este permiso permite bloquear el almacén de claves, es decir, expulsar la clave maestra, de modo que las claves vinculadas de autenticación no se puedan usar ni crear. |
reset
|
Este permiso permite restablecer el almacén de claves a los valores predeterminados de fábrica y borrar todas las claves que no son vitales para el funcionamiento del SO Android. |
unlock
|
Este permiso es necesario para intentar desbloquear la clave maestra de las claves vinculadas a la autenticación. |